Skip to content

Commit

Permalink
Reimplement goTag using FieldMutateHook (#1682)
Browse files Browse the repository at this point in the history
* Reimplement goTag using a FieldMutateHook

This change does not change the logic of goTag, merely reimplements it using a FieldMutateHook and sets it as the default FieldMutateHook for the modelgen plugin.

* Add repeated tag test
  • Loading branch information
tprebs committed Nov 1, 2021
1 parent 37a4e7e commit 59a3091
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 74 deletions.
52 changes: 10 additions & 42 deletions codegen/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,6 @@ func (c *Config) injectTypesFromSchema() error {
SkipRuntime: true,
}

c.Directives["goTag"] = DirectiveConfig{
SkipRuntime: true,
}

for _, schemaType := range c.Schema.Types {
if schemaType == c.Schema.Query || schemaType == c.Schema.Mutation || schemaType == c.Schema.Subscription {
continue
Expand All @@ -265,15 +261,9 @@ func (c *Config) injectTypesFromSchema() error {

if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject {
for _, field := range schemaType.Fields {
typeMapField := TypeMapField{
ExtraTags: c.Models[schemaType.Name].Fields[field.Name].ExtraTags,
FieldName: c.Models[schemaType.Name].Fields[field.Name].FieldName,
Resolver: c.Models[schemaType.Name].Fields[field.Name].Resolver,
}
directive := false
if fd := field.Directives.ForName("goField"); fd != nil {
forceResolver := typeMapField.Resolver
fieldName := typeMapField.FieldName
forceResolver := c.Models[schemaType.Name].Fields[field.Name].Resolver
fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName

if ra := fd.Arguments.ForName("forceResolver"); ra != nil {
if fr, err := ra.Value.Value(nil); err == nil {
Expand All @@ -287,38 +277,17 @@ func (c *Config) injectTypesFromSchema() error {
}
}

typeMapField.FieldName = fieldName
typeMapField.Resolver = forceResolver
directive = true
}

for _, goTag := range field.Directives.ForNames("goTag") {
key := ""
value := field.Name
if arg := goTag.Arguments.ForName("key"); arg != nil {
if k, err := arg.Value.Value(nil); err == nil {
key = k.(string)
}
}

if arg := goTag.Arguments.ForName("value"); arg != nil {
if v, err := arg.Value.Value(nil); err == nil {
value = v.(string)
}
}

typeMapField.ExtraTags = append(typeMapField.ExtraTags, key+":\""+value+"\"")
directive = true
}

if directive {
if c.Models[schemaType.Name].Fields == nil {
c.Models[schemaType.Name] = TypeMapEntry{
Model: c.Models[schemaType.Name].Model,
Fields: map[string]TypeMapField{},
}
}
c.Models[schemaType.Name].Fields[field.Name] = typeMapField

c.Models[schemaType.Name].Fields[field.Name] = TypeMapField{
FieldName: fieldName,
Resolver: forceResolver,
}
}
}
}
Expand All @@ -333,10 +302,9 @@ type TypeMapEntry struct {
}

type TypeMapField struct {
Resolver bool `yaml:"resolver"`
FieldName string `yaml:"fieldName"`
ExtraTags []string `yaml:"extraTags"`
GeneratedMethod string `yaml:"-"`
Resolver bool `yaml:"resolver"`
FieldName string `yaml:"fieldName"`
GeneratedMethod string `yaml:"-"`
}

type StringList []string
Expand Down
4 changes: 3 additions & 1 deletion docs/content/recipes/modelgen-hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ type ObjectInput struct {
}
```

If a constraint being used during generation shoud not be published during introspection, the directive should be listed with `skip_runtime:true` in gqlgen.yml
If a constraint being used during generation should not be published during introspection, the directive should be listed with `skip_runtime:true` in gqlgen.yml
```yaml
directives:
constraint:
skip_runtime: true
```

The built-in directive `@goTag` is implemented using the FieldMutateHook. See: `plugin/modelgen/models.go` function `GoTagFieldHook`
39 changes: 33 additions & 6 deletions plugin/modelgen/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ type BuildMutateHook = func(b *ModelBuild) *ModelBuild

type FieldMutateHook = func(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error)

// defaultFieldMutateHook is the default hook for the Plugin which applies the GoTagFieldHook.
func defaultFieldMutateHook(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error) {
return f, nil
return GoTagFieldHook(td, fd, f)
}

func defaultBuildMutateHook(b *ModelBuild) *ModelBuild {
return b
}
Expand Down Expand Up @@ -171,14 +171,11 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
typ = types.NewPointer(typ)
}

tags := []string{`json:"` + field.Name + `"`}
tags = append(tags, cfg.Models[schemaType.Name].Fields[field.Name].ExtraTags...)

f := &Field{
Name: name,
Type: typ,
Description: field.Description,
Tag: strings.Join(tags, " "),
Tag: `json:"` + field.Name + `"`,
}

if m.FieldHook != nil {
Expand Down Expand Up @@ -254,6 +251,36 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
return nil
}

// GoTagFieldHook applies the goTag directive to the generated Field f. When applying the Tag to the field, the field
// name is used when no value argument is present.
func GoTagFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error) {
args := make([]string, 0)
for _, goTag := range fd.Directives.ForNames("goTag") {
key := ""
value := fd.Name

if arg := goTag.Arguments.ForName("key"); arg != nil {
if k, err := arg.Value.Value(nil); err == nil {
key = k.(string)
}
}

if arg := goTag.Arguments.ForName("value"); arg != nil {
if v, err := arg.Value.Value(nil); err == nil {
value = v.(string)
}
}

args = append(args, key+":\""+value+"\"")
}

if len(args) > 0 {
f.Tag = f.Tag + " " + strings.Join(args, " ")
}

return f, nil
}

func isStruct(t types.Type) bool {
_, is := t.Underlying().(*types.Struct)
return is
Expand Down
22 changes: 3 additions & 19 deletions plugin/modelgen/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"strings"
"testing"

"github.com/vektah/gqlparser/v2/ast"

"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin/modelgen/out"
"github.com/stretchr/testify/require"
Expand All @@ -20,7 +18,7 @@ func TestModelGeneration(t *testing.T) {
require.NoError(t, cfg.Init())
p := Plugin{
MutateHook: mutateHook,
FieldHook: mutateFieldHook,
FieldHook: defaultFieldMutateHook,
}
require.NoError(t, p.MutateConfig(cfg))

Expand Down Expand Up @@ -77,6 +75,8 @@ func TestModelGeneration(t *testing.T) {
expectedTags := []string{
`json:"name" anotherTag:"tag"`,
`json:"enum" yetAnotherTag:"12"`,
`json:"noVal" yaml:"noVal"`,
`json:"repeated" someTag:"value" repeated:"true"`,
}

for _, tag := range expectedTags {
Expand All @@ -98,19 +98,3 @@ func mutateHook(b *ModelBuild) *ModelBuild {

return b
}

func mutateFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error) {

if fd.Directives == nil || td.Name != "FieldMutationHook" {
return f, nil
}

directive := fd.Directives.ForName("addTag")
if directive != nil {
args := directive.ArgumentMap(map[string]interface{}{})
if tag, ok := args["tag"]; ok {
f.Tag += " " + tag.(string)
}
}
return f, nil
}
6 changes: 4 additions & 2 deletions plugin/modelgen/out/generated.go

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

13 changes: 9 additions & 4 deletions plugin/modelgen/testdata/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
directive @addTag(tag: String!) on INPUT_FIELD_DEFINITION
| FIELD_DEFINITION
directive @goTag(
key: String!
value: String
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION

type Query {
thisShoudlntGetGenerated: Boolean
Expand Down Expand Up @@ -58,8 +60,11 @@ input ExistingInput {
}

type FieldMutationHook {
name: String @addTag(tag :"anotherTag:\"tag\"")
enum: ExistingEnum @addTag(tag: "yetAnotherTag:\"12\"")
name: String @goTag(key :"anotherTag", value: "tag")
enum: ExistingEnum @goTag(key: "yetAnotherTag", value: "12")
noVal: String @goTag(key: "yaml") @goTag(key : "repeated", value: "true")
repeated: String @goTag(key: "someTag", value: "value") @goTag(key : "repeated", value: "true")

}

enum ExistingEnum {
Expand Down

0 comments on commit 59a3091

Please sign in to comment.