gqlgen icon indicating copy to clipboard operation
gqlgen copied to clipboard

Add field info to directive context

Open seaskyways opened this issue 4 years ago • 9 comments

What happened?

I was writing a directive to validate fields. GQLgen did not give me the functionality I need. I have the following directive that applies on INPUT_FIELD_DEFINITION:

func (r resolver) directiveValidate(
	ctx context.Context, obj interface{}, next graphql.Resolver, rules string,
) (res interface{}, err error) {
	v := validator.New()
	err = v.VarCtx(ctx, obj, rules)
	if err != nil {
		return
	}

	return next(ctx)
}

Unfortunately there is no way for me to check which field is this directive running on right now. obj is pointing to the parent object instead of the field the directive is on.

What did you expect?

I expect to receive in the directive information regarding the field related perhaps in the function context. What I need is the field's name as in the schema and the field's value either provided in ctx or obj.

Minimal graphql.schema and models to reproduce

directive @validate(rules: String!) on INPUT_FIELD_DEFINITION
input Login {
  # unless I explicitly pass to the directive the field name, there is no way to know what field is being "validated" when the directive runs. 
  username: String! @validate(rules: "min=6")
}

versions

  • gqlgen version? 0.11.1
  • go version? 1.13.8
  • dep or go modules? go modules

seaskyways avatar Mar 03 '20 21:03 seaskyways

@vektah , @seaskyways, We encountered this issue as well: https://github.com/99designs/gqlgen/issues/990#issuecomment-585550923

Adding it here for completeness

swkumar avatar Mar 09 '20 19:03 swkumar

Hi @seaskyways @vektah, Is there an ETA for this?

Gilwe avatar Apr 20 '20 13:04 Gilwe

Is there an ETA for this?

Seems, its abandoned.

logrusorgru avatar Dec 10 '20 13:12 logrusorgru

Is there an ETA for this?

Seems, its abandoned.

It's not. It's going a little slow lately, but they'll be back brighter than before!

frederikhors avatar Dec 10 '20 15:12 frederikhors

Hi,

We have identified an approach that works for us at i2O with gqlgen version 0.13.0. It relies on a built-in directive which we noticed is added to all fields as part of the generated code (generated.go).

The directive returns the value of the actual field which can then be used and modified accordingly.

func ToLowerDirective(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
	// Run built-in gqlgen directive first
	obj, err = next(ctx)
	if err != nil {
		return nil, err
	}

	pathContext := graphql.GetPathContext(ctx)
	if pathContext != nil {
		// Directive is being applied to an input field or argument
		// obj here is the value of the input / argument after it has been resolved by the built-in gqlgen directive

		fieldName := *pathContext.Field

		switch value := obj.(type) {
		case string:
			return strings.ToLower(value), nil
		default:
			return nil, errors.Errorf("input field or argument \"%s\" must be a string", fieldName)
		}
	} else {
		// Directive is being applied to a returned entity's field
		// obj here is the value of the field after it has been resolved by the built-in gqlgen directive

		fieldContext := graphql.GetFieldContext(ctx)
		fieldName := fieldContext.Field.Name

		switch value := obj.(type) {
		case string:
			return strings.ToLower(value), nil
		default:
			return nil, errors.Errorf("field \"%s\" must be a string", fieldName)
		}
	}
}

muhammad-i2o avatar Dec 10 '20 16:12 muhammad-i2o

Here is how I solved it. Pretty similar to @muhammad-i2o's solution but it is all you need if your directive is applied on INPUT_FIELD_DEFINITION.

config.Directives.Validate = func(ctx context.Context, obj interface{}, next graphql.Resolver, rules *string) (interface{}, error) {
	val, err := next(ctx)
	if err != nil {
		return val, err
	}
	
	fmt.Println(">>> fieldname =", *graphql.GetPathContext(ctx).Field)
	fmt.Println(">>> fieldvalue =", val)
	fmt.Println(">>> rules =", *rules)

	return val, nil
}

appcypher avatar Mar 10 '21 16:03 appcypher

I just tried this and everything looks good, I can use validation on input fields, we don't really need field name, do we, we just need the rules and the actual value,

cyberhck avatar Mar 23 '21 09:03 cyberhck

The field name is useful for error handling.

appcypher avatar Mar 24 '21 22:03 appcypher

ahh, I'm actually accepting a error message as second argument :)

cyberhck avatar Mar 25 '21 07:03 cyberhck