Gqlgen: How to get attribute name/ key in directive?

Created on 13 Apr 2020  ·  12Comments  ·  Source: 99designs/gqlgen

In a directive func, how do I get the name of its input field?

input ClientInput {
    first_name: String! @required
    ...
}
func(ctx context.Context, obj interface{}, next graphql.Resolver) (interface{}, error) {
    // How do I get "first_name" from here?
}

I have gone through anything that I could get from the variables I have. I have found that you get the attribute value through next(ctx) and you get the field name through graphql.GetFieldContext(ctx).Field.Name (but that would be the name of the mutation or where ever the input field has been used). I have found no possibility to retrieve that value.

Most helpful comment

You can access the field name with graphql.GetPathContext(ctx).Field

All 12 comments

Did you find a way of doing this?
I've been having to manually edit the generated file in order to pass the data through the "obj" field, but this is a hack

Did you find a way of doing this?

I've been having to manually edit the generated file in order to pass the data through the "obj" field, but this is a hack

No, I have not and I‘m still interested in a solution 😄

What temporarily worked for me was to dive into the generated code and manually inject the data
Under the respective directive, there is ... know what, lemme just post the generated code and what I modify so you can see for yourself

Old Code

func (ec *executionContext) _Query_timeline(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
    defer func() {
        if r := recover(); r != nil {
            ec.Error(ctx, ec.Recover(ctx, r))
            ret = graphql.Null
        }
    }()
    fc := &graphql.FieldContext{
        Object:   "Query",
        Field:    field,
        Args:     nil,
        IsMethod: true,
    }

    ctx = graphql.WithFieldContext(ctx, fc)
    rawArgs := field.ArgumentMap(ec.Variables)
    args, err := ec.field_Query_timeline_args(ctx, rawArgs)
    if err != nil {
        ec.Error(ctx, err)
        return graphql.Null
    }
    fc.Args = args
    resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
        directive0 := func(rctx context.Context) (interface{}, error) {
            ctx = rctx // use context from middleware stack in children
            return ec.resolvers.Query().Timeline(rctx, args["topic"].(models.Topic), args["limit"].(*int64), args["olderThan"].(*string))
        }
        directive1 := func(ctx context.Context) (interface{}, error) {
            if ec.directives.IsAuthorisedTimelineQuery == nil {
                return nil, errors.New("directive isAuthorisedTimelineQuery is not implemented")
            }
            return ec.directives.IsAuthorisedTimelineQuery(ctx, nil, directive0)
        }

        tmp, err := directive1(rctx)
        if err != nil {
            return nil, err
        }
        if tmp == nil {
            return nil, nil
        }
        if data, ok := tmp.(*models.TimelineQuery); ok {
            return data, nil
        }
        return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/Zero-Q-Ltd/2bonge-back/models.TimelineQuery`, tmp)
    })
    if err != nil {
        ec.Error(ctx, err)
        return graphql.Null
    }
    if resTmp == nil {
        if !graphql.HasFieldError(ctx, fc) {
            ec.Errorf(ctx, "must not be null")
        }
        return graphql.Null
    }
    res := resTmp.(*models.TimelineQuery)
    fc.Result = res
    return ec.marshalNTimelineQuery2ᚖgithubᚗcomᚋZeroᚑQᚑLtdᚋ2bongeᚑbackᚋmodelsᚐTimelineQuery(ctx, field.Selections, res)
}

Modified code:

func (ec *executionContext) _Query_timeline(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
    defer func() {
        if r := recover(); r != nil {
            ec.Error(ctx, ec.Recover(ctx, r))
            ret = graphql.Null
        }
    }()
    fc := &graphql.FieldContext{
        Object:   "Query",
        Field:    field,
        Args:     nil,
        IsMethod: true,
    }

    ctx = graphql.WithFieldContext(ctx, fc)
    rawArgs := field.ArgumentMap(ec.Variables)
    args, err := ec.field_Query_timeline_args(ctx, rawArgs)
    if err != nil {
        ec.Error(ctx, err)
        return graphql.Null
    }
    fc.Args = args
    resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
        directive0 := func(rctx context.Context) (interface{}, error) {
            ctx = rctx // use context from middleware stack in children
            return ec.resolvers.Query().Timeline(rctx, args["topic"].(models.Topic), args["limit"].(*int64), args["olderThan"].(*string))
        }
            directive1 := func(ctx context.Context) (interface{}, error) {
                if ec.directives.IsAuthorisedTimelineQuery == nil {
                    return nil, errors.New("directive isAuthorisedTimelineQuery is not implemented")
                }
                return ec.directives.IsAuthorisedTimelineQuery(ctx, args["params"].(models.TimelineQueryParams), directive0) // <---- THIS LINE HERE IS DEAFULT NIL
            }
        tmp, err := directive1(rctx)
        if err != nil {
            return nil, err
        }
        if tmp == nil {
            return nil, nil
        }
        if data, ok := tmp.(*models.TimelineQuery); ok {
            return data, nil
        }
        return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/Zero-Q-Ltd/2bonge-back/models.TimelineQuery`, tmp)
    })
    if err != nil {
        ec.Error(ctx, err)
        return graphql.Null
    }
    if resTmp == nil {
        if !graphql.HasFieldError(ctx, fc) {
            ec.Errorf(ctx, "must not be null")
        }
        return graphql.Null
    }
    res := resTmp.(*models.TimelineQuery)
    fc.Result = res
    return ec.marshalNTimelineQuery2ᚖgithubᚗcomᚋZeroᚑQᚑLtdᚋ2bongeᚑbackᚋmodelsᚐTimelineQuery(ctx, field.Selections, res)
}

This ONLY works if your directive is of type FIELD_DEFINITION, although I'm sure a similar "hack" can be found for other types
Also note that this seems like a known issue according to this documentation, although I think a better job can be done

Perfect! Thank you very much for sharing. Editing the generated code is not an option for me, but this looks like an easy pull request to me.

Actually, turns out this was already implemented, but maybe not properly documented, as I came across it by mistake in this comment by @vektah
Please take a look, and possibly close this issue or change the title to one that requests better documentation
using graphql.GetFieldContext(ctx).Args will give you the correct variables

Hello @kisinga , thanks for reaching back.

@vektah 's suggestion I think was graphql.GetFieldContext(ctx).Field.Name, which will give me createIngredient, which is the name of my mutation method.

You suggested graphql.GetFieldContext(ctx).Args. This will always give me an empty map map[].

Hello @kisinga , thanks for reaching back.

@vektah 's suggestion I think was graphql.GetFieldContext(ctx).Field.Name, which will give me createIngredient, which is the name of my mutation method.

You suggested graphql.GetFieldContext(ctx).Args. This will always give me an empty map map[].

I have used the same and it yields a map of the values
Have you tried it?

Of course I did. At least that is what I get on log.Println.

My query looks like this btw:

mutation {
  createIngredient(input:{
    name: "", // this has the @required directive
    ...
  }
}

Hm okay yeah that's weird. I have noted your contact, you may delete it. I'm currently working on a different task, so I might not get in touch with you today already. Thank you very much❣️

You can access the field name with graphql.GetPathContext(ctx).Field

That's it!!! Thank you @MichalZalecki

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cajax picture cajax  ·  4Comments

steebchen picture steebchen  ·  3Comments

coderste picture coderste  ·  3Comments

msmedes picture msmedes  ·  4Comments

itsbalamurali picture itsbalamurali  ·  4Comments