Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a plugin for configuring gqlgen via directives #732

Merged
merged 5 commits into from
Jun 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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(),
}
Expand Down
2 changes: 1 addition & 1 deletion codegen/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
}
Expand Down
28 changes: 22 additions & 6 deletions codegen/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,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"}
Expand All @@ -34,6 +35,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,
},
},
}
}

Expand Down Expand Up @@ -299,6 +311,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 {
Expand Down
13 changes: 2 additions & 11 deletions codegen/directive.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -60,11 +55,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)
Expand Down Expand Up @@ -92,7 +82,7 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) {
DirectiveDefinition: dir,
Name: name,
Args: args,
Builtin: builtin,
Builtin: b.Config.Directives[name].SkipRuntime,
}
}

Expand Down Expand Up @@ -132,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,
}

}
Expand Down
2 changes: 1 addition & 1 deletion codegen/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
}
Expand Down
30 changes: 30 additions & 0 deletions docs/content/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
```
7 changes: 0 additions & 7 deletions example/config/.gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
44 changes: 40 additions & 4 deletions example/config/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions example/config/schema.graphql
Original file line number Diff line number Diff line change
@@ -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!]!
}
Expand Down
2 changes: 1 addition & 1 deletion example/config/todo.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type Todo {
id: ID!
id: ID! @goField(forceResolver: true)
databaseId: Int!
text: String!
done: Boolean!
Expand Down
5 changes: 3 additions & 2 deletions example/config/user.graphql
Original file line number Diff line number Diff line change
@@ -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")
}
93 changes: 93 additions & 0 deletions plugin/schemaconfig/schemaconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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.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
}