From 17a82c37e86df494354c92de4b306a15c11747ee Mon Sep 17 00:00:00 2001 From: Luke Cawood Date: Mon, 27 May 2019 15:03:22 +1000 Subject: [PATCH 1/4] Provide config to skip generating runtime for a directive --- codegen/config/config.go | 28 ++++++++++++++++++++++------ codegen/directive.go | 7 +------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/codegen/config/config.go b/codegen/config/config.go index 1725adab0e8..a0115c530ac 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -17,12 +17,13 @@ import ( ) type Config struct { - SchemaFilename StringList `yaml:"schema,omitempty"` - Exec PackageConfig `yaml:"exec"` - Model PackageConfig `yaml:"model"` - Resolver PackageConfig `yaml:"resolver,omitempty"` - Models TypeMap `yaml:"models,omitempty"` - StructTag string `yaml:"struct_tag,omitempty"` + SchemaFilename StringList `yaml:"schema,omitempty"` + Exec PackageConfig `yaml:"exec"` + Model PackageConfig `yaml:"model"` + Resolver PackageConfig `yaml:"resolver,omitempty"` + Models TypeMap `yaml:"models,omitempty"` + StructTag string `yaml:"struct_tag,omitempty"` + Directives map[string]DirectiveConfig `yaml:"directives,omitempty"` } var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} @@ -33,6 +34,17 @@ func DefaultConfig() *Config { SchemaFilename: StringList{"schema.graphql"}, Model: PackageConfig{Filename: "models_gen.go"}, Exec: PackageConfig{Filename: "generated.go"}, + Directives: map[string]DirectiveConfig{ + "skip": { + SkipRuntime: true, + }, + "include": { + SkipRuntime: true, + }, + "deprecated": { + SkipRuntime: true, + }, + }, } } @@ -265,6 +277,10 @@ func (tm TypeMap) Add(Name string, goType string) { tm[Name] = modelCfg } +type DirectiveConfig struct { + SkipRuntime bool `yaml:"skip_runtime"` +} + func inStrSlice(haystack []string, needle string) bool { for _, v := range haystack { if needle == v { diff --git a/codegen/directive.go b/codegen/directive.go index 5a27e8ace71..7c43d84aca5 100644 --- a/codegen/directive.go +++ b/codegen/directive.go @@ -24,11 +24,6 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) { return nil, errors.Errorf("directive with name %s already exists", name) } - var builtin bool - if name == "skip" || name == "include" || name == "deprecated" { - builtin = true - } - var args []*FieldArgument for _, arg := range dir.Arguments { tr, err := b.Binder.TypeReference(arg.Type, nil) @@ -55,7 +50,7 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) { directives[name] = &Directive{ Name: name, Args: args, - Builtin: builtin, + Builtin: b.Config.Directives[name].SkipRuntime, } } From de75743c1169cc0e51de0049e78c7f7c0bd92cef Mon Sep 17 00:00:00 2001 From: Luke Cawood Date: Mon, 27 May 2019 15:04:18 +1000 Subject: [PATCH 2/4] Add plugin for providing config via schema directives --- api/generate.go | 2 + example/config/.gqlgen.yml | 7 --- example/config/generated.go | 44 +++++++++++-- example/config/schema.graphql | 3 + example/config/todo.graphql | 2 +- example/config/user.graphql | 5 +- plugin/schemaconfig/schemaconfig.go | 95 +++++++++++++++++++++++++++++ 7 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 plugin/schemaconfig/schemaconfig.go diff --git a/api/generate.go b/api/generate.go index 3dd083f52f6..3256bdc3de1 100644 --- a/api/generate.go +++ b/api/generate.go @@ -8,6 +8,7 @@ import ( "github.com/99designs/gqlgen/plugin" "github.com/99designs/gqlgen/plugin/modelgen" "github.com/99designs/gqlgen/plugin/resolvergen" + "github.com/99designs/gqlgen/plugin/schemaconfig" "github.com/pkg/errors" "golang.org/x/tools/go/packages" ) @@ -17,6 +18,7 @@ func Generate(cfg *config.Config, option ...Option) error { _ = syscall.Unlink(cfg.Model.Filename) plugins := []plugin.Plugin{ + schemaconfig.New(), modelgen.New(), resolvergen.New(), } diff --git a/example/config/.gqlgen.yml b/example/config/.gqlgen.yml index a710b9c7903..ccea6688f39 100644 --- a/example/config/.gqlgen.yml +++ b/example/config/.gqlgen.yml @@ -15,16 +15,9 @@ resolver: models: Todo: # Object fields: - id: - resolver: true text: fieldName: Description # Field NewTodo: # Input fields: userId: fieldName: UserID # Field - User: - model: github.com/99designs/gqlgen/example/config.User - fields: - name: - fieldName: FullName # Method diff --git a/example/config/generated.go b/example/config/generated.go index ccf93600be2..666914062f5 100644 --- a/example/config/generated.go +++ b/example/config/generated.go @@ -235,7 +235,10 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var parsedSchema = gqlparser.MustLoadSchema( - &ast.Source{Name: "schema.graphql", Input: `type Query { + &ast.Source{Name: "schema.graphql", Input: `directive @goModel(model: String, models: [String!]) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION +directive @goField(forceResolver: Boolean, name: String) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION + +type Query { todos: [Todo!]! } @@ -244,7 +247,7 @@ type Mutation { } `}, &ast.Source{Name: "todo.graphql", Input: `type Todo { - id: ID! + id: ID! @goField(forceResolver: true) databaseId: Int! text: String! done: Boolean! @@ -257,9 +260,10 @@ input NewTodo { } `}, - &ast.Source{Name: "user.graphql", Input: `type User { + &ast.Source{Name: "user.graphql", Input: `type User +@goModel(model:"github.com/99designs/gqlgen/example/config.User") { id: ID! - name: String! + name: String! @goField(name:"FullName") } `}, ) @@ -2286,6 +2290,38 @@ func (ec *executionContext) marshalOString2string(ctx context.Context, sel ast.S return graphql.MarshalString(v) } +func (ec *executionContext) unmarshalOString2ᚕstring(ctx context.Context, v interface{}) ([]string, error) { + var vSlice []interface{} + if v != nil { + if tmp1, ok := v.([]interface{}); ok { + vSlice = tmp1 + } else { + vSlice = []interface{}{v} + } + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + res[i], err = ec.unmarshalNString2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalOString2ᚕstring(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNString2string(ctx, sel, v[i]) + } + + return ret +} + func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) { if v == nil { return nil, nil diff --git a/example/config/schema.graphql b/example/config/schema.graphql index 3485fb1c673..4e7b69fb054 100644 --- a/example/config/schema.graphql +++ b/example/config/schema.graphql @@ -1,3 +1,6 @@ +directive @goModel(model: String, models: [String!]) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION +directive @goField(forceResolver: Boolean, name: String) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION + type Query { todos: [Todo!]! } diff --git a/example/config/todo.graphql b/example/config/todo.graphql index ccee3e44dc7..8e7c8aa0aa1 100644 --- a/example/config/todo.graphql +++ b/example/config/todo.graphql @@ -1,5 +1,5 @@ type Todo { - id: ID! + id: ID! @goField(forceResolver: true) databaseId: Int! text: String! done: Boolean! diff --git a/example/config/user.graphql b/example/config/user.graphql index 321de95b1e7..52c688bd331 100644 --- a/example/config/user.graphql +++ b/example/config/user.graphql @@ -1,4 +1,5 @@ -type User { +type User +@goModel(model:"github.com/99designs/gqlgen/example/config.User") { id: ID! - name: String! + name: String! @goField(name:"FullName") } diff --git a/plugin/schemaconfig/schemaconfig.go b/plugin/schemaconfig/schemaconfig.go new file mode 100644 index 00000000000..8068d8f44e5 --- /dev/null +++ b/plugin/schemaconfig/schemaconfig.go @@ -0,0 +1,95 @@ +package schemaconfig + +import ( + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/plugin" + "github.com/vektah/gqlparser/ast" +) + +func New() plugin.Plugin { + return &Plugin{} +} + +type Plugin struct{} + +var _ plugin.ConfigMutator = &Plugin{} + +func (m *Plugin) Name() string { + return "schemaconfig" +} + +func (m *Plugin) MutateConfig(cfg *config.Config) error { + if err := cfg.Check(); err != nil { + return err + } + + schema, _, err := cfg.LoadSchema() + if err != nil { + return err + } + + cfg.InjectBuiltins(schema) + + cfg.Directives["goModel"] = config.DirectiveConfig{ + SkipRuntime: true, + } + + cfg.Directives["goField"] = config.DirectiveConfig{ + SkipRuntime: true, + } + + for _, schemaType := range schema.Types { + if schemaType == schema.Query || schemaType == schema.Mutation || schemaType == schema.Subscription { + continue + } + + if bd := schemaType.Directives.ForName("goModel"); bd != nil { + if ma := bd.Arguments.ForName("model"); ma != nil { + if mv, err := ma.Value.Value(nil); err == nil { + cfg.Models.Add(schemaType.Name, mv.(string)) + } + } + if ma := bd.Arguments.ForName("models"); ma != nil { + if mvs, err := ma.Value.Value(nil); err == nil { + for _, mv := range mvs.([]interface{}) { + cfg.Models.Add(schemaType.Name, mv.(string)) + } + } + } + } + + if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject { + for _, field := range schemaType.Fields { + if fd := field.Directives.ForName("goField"); fd != nil { + forceResolver := cfg.Models[schemaType.Name].Fields[field.Name].Resolver + fieldName := cfg.Models[schemaType.Name].Fields[field.Name].FieldName + + if ra := fd.Arguments.ForName("forceResolver"); ra != nil { + if fr, err := ra.Value.Value(nil); err == nil { + forceResolver = fr.(bool) + } + } + + if na := fd.Arguments.ForName("name"); na != nil { + if fr, err := na.Value.Value(nil); err == nil { + fieldName = fr.(string) + } + } + + if cfg.Models[schemaType.Name].Fields == nil { + cfg.Models[schemaType.Name] = config.TypeMapEntry{ + Model: cfg.Models[schemaType.Name].Model, + Fields: map[string]config.TypeMapField{}, + } + } + + cfg.Models[schemaType.Name].Fields[field.Name] = config.TypeMapField{ + FieldName: fieldName, + Resolver: forceResolver, + } + } + } + } + } + return nil +} From 9be5aad0cf295796e01ea0955ff53f944a8c5cb9 Mon Sep 17 00:00:00 2001 From: Luke Cawood Date: Wed, 5 Jun 2019 17:45:25 +1000 Subject: [PATCH 3/4] Don't inject builtins during schema config --- plugin/schemaconfig/schemaconfig.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugin/schemaconfig/schemaconfig.go b/plugin/schemaconfig/schemaconfig.go index 8068d8f44e5..1fcf4105049 100644 --- a/plugin/schemaconfig/schemaconfig.go +++ b/plugin/schemaconfig/schemaconfig.go @@ -28,8 +28,6 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { return err } - cfg.InjectBuiltins(schema) - cfg.Directives["goModel"] = config.DirectiveConfig{ SkipRuntime: true, } From c14f8650d72591d6ae7a75e904118c05cf1e291f Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 26 Jun 2019 17:07:00 +1000 Subject: [PATCH 4/4] Add docs --- codegen/args.go | 2 +- codegen/directive.go | 6 +----- codegen/field.go | 2 +- docs/content/config.md | 30 ++++++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/codegen/args.go b/codegen/args.go index 905b4625eef..1d3e51aa3ad 100644 --- a/codegen/args.go +++ b/codegen/args.go @@ -30,7 +30,7 @@ type FieldArgument struct { func (f *FieldArgument) ImplDirectives() []*Directive { d := make([]*Directive, 0) for i := range f.Directives { - if !f.Directives[i].IsBuiltin() && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) { + if !f.Directives[i].Builtin && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) { d = append(d, f.Directives[i]) } } diff --git a/codegen/directive.go b/codegen/directive.go index 888d32e4bc1..491f92f43ac 100644 --- a/codegen/directive.go +++ b/codegen/directive.go @@ -24,11 +24,6 @@ type Directive struct { Builtin bool } -//IsBuiltin check directive -func (d *Directive) IsBuiltin() bool { - return d.Builtin || d.Name == "skip" || d.Name == "include" || d.Name == "deprecated" -} - //IsLocation check location directive func (d *Directive) IsLocation(location ...ast.DirectiveLocation) bool { for _, l := range d.Locations { @@ -127,6 +122,7 @@ func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) { Name: d.Name, Args: args, DirectiveDefinition: list[i].Definition, + Builtin: b.Config.Directives[d.Name].SkipRuntime, } } diff --git a/codegen/field.go b/codegen/field.go index 8d02664daee..e51c11e3091 100644 --- a/codegen/field.go +++ b/codegen/field.go @@ -298,7 +298,7 @@ func (f *Field) ImplDirectives() []*Directive { loc = ast.LocationInputFieldDefinition } for i := range f.Directives { - if !f.Directives[i].IsBuiltin() && f.Directives[i].IsLocation(loc) { + if !f.Directives[i].Builtin && f.Directives[i].IsLocation(loc) { d = append(d, f.Directives[i]) } } diff --git a/docs/content/config.md b/docs/content/config.md index eee5cb60520..a12f406a9e5 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -66,3 +66,33 @@ models: Everything has defaults, so add things as you need. +## Inline config with directives + +gqlgen ships with some builtin directives that make it a little easier to manage wiring. + +To start using them you first need to define them: +```graphql +directive @goModel(model: String, models: [String!]) on OBJECT + | INPUT_OBJECT + | SCALAR + | ENUM + | INTERFACE + | UNION + +directive @goField(forceResolver: Boolean, name: String) on INPUT_FIELD_DEFINITION + | FIELD_DEFINITION +``` + +> Here be dragons +> +> gqlgen doesnt currently support user-configurable directives for SCALAR, ENUM, INTERFACE or UNION. This only works +> for internal directives. You can track the progress [here](https://github.com/99designs/gqlgen/issues/760) + +Now you can use these directives when defining types in your schema: + +```graphql +type User @goModel(model:"github.com/my/app/models.User") { + id: ID! @goField(name:"todoId") + name: String! @goField(resolver: true) +} +```