github.com/operandinc/gqlgen@v0.16.1/docs/content/recipes/modelgen-hook.md (about) 1 --- 2 title: "Allowing mutation of generated models before rendering" 3 description: How to use a model mutation function to insert a ORM-specific tags onto struct fields. 4 linkTitle: "Modelgen hook" 5 menu: { main: { parent: "recipes" } } 6 --- 7 8 ## BuildMutateHook 9 10 The following recipe shows how to use a `modelgen` plugin hook to mutate generated 11 models before they are rendered into a resulting file. This feature has many uses but 12 the example focuses only on inserting ORM-specific tags into generated struct fields. This 13 is a common use case since it allows for better field matching of DB queries and 14 the generated data structure. 15 16 First of all, we need to create a function that will mutate the generated model. 17 Then we can attach the function to the plugin and use it like any other plugin. 18 19 ```go 20 import ( 21 "fmt" 22 "os" 23 24 "github.com/operandinc/gqlgen/api" 25 "github.com/operandinc/gqlgen/codegen/config" 26 "github.com/operandinc/gqlgen/plugin/modelgen" 27 ) 28 29 // Defining mutation function 30 func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild { 31 for _, model := range b.Models { 32 for _, field := range model.Fields { 33 field.Tag += ` orm_binding:"` + model.Name + `.` + field.Name + `"` 34 } 35 } 36 37 return b 38 } 39 40 func main() { 41 cfg, err := config.LoadConfigFromDefaultLocations() 42 if err != nil { 43 fmt.Fprintln(os.Stderr, "failed to load config", err.Error()) 44 os.Exit(2) 45 } 46 47 // Attaching the mutation function onto modelgen plugin 48 p := modelgen.Plugin{ 49 MutateHook: mutateHook, 50 } 51 52 err = api.Generate(cfg, api.ReplacePlugin(&p)) 53 54 if err != nil { 55 fmt.Fprintln(os.Stderr, err.Error()) 56 os.Exit(3) 57 } 58 } 59 ``` 60 61 Now fields from generated models will contain a additional tag `orm_binding`. 62 63 This schema: 64 65 ```graphql 66 type Object { 67 field1: String 68 field2: Int 69 } 70 ``` 71 72 Will gen generated into: 73 74 ```go 75 type Object struct { 76 field1 *string `json:"field1" orm_binding:"Object.field1"` 77 field2 *int `json:"field2" orm_binding:"Object.field2"` 78 } 79 ``` 80 81 ## FieldMutateHook 82 83 For more fine grained control over model generation, a graphql schema aware a FieldHook can be provided. This hook has access to type and field graphql definitions enabling the hook to modify the `modelgen.Field` using directives defined within the schema. 84 85 The below recipe uses this feature to add validate tags to the generated model for use with `go-playground/validator` where the validate tags are defined in a constraint directive in the schema. 86 87 ```go 88 import ( 89 "fmt" 90 "github.com/vektah/gqlparser/v2/ast" 91 "os" 92 93 "github.com/operandinc/gqlgen/api" 94 "github.com/operandinc/gqlgen/codegen/config" 95 "github.com/operandinc/gqlgen/plugin/modelgen" 96 ) 97 98 // Defining mutation function 99 func constraintFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *modelgen.Field) (*modelgen.Field, error) { 100 101 c := fd.Directives.ForName("constraint") 102 if c != nil { 103 formatConstraint := c.Arguments.ForName("format") 104 105 if formatConstraint != nil{ 106 f.Tag += " validate:"+formatConstraint.Value.String() 107 } 108 109 } 110 111 return f, nil 112 } 113 114 func main() { 115 cfg, err := config.LoadConfigFromDefaultLocations() 116 if err != nil { 117 fmt.Fprintln(os.Stderr, "failed to load config", err.Error()) 118 os.Exit(2) 119 } 120 121 // Attaching the mutation function onto modelgen plugin 122 p := modelgen.Plugin{ 123 FieldHook: constraintFieldHook, 124 } 125 126 err = api.Generate(cfg, api.ReplacePlugin(&p)) 127 128 if err != nil { 129 fmt.Fprintln(os.Stderr, err.Error()) 130 os.Exit(3) 131 } 132 } 133 ``` 134 135 This schema: 136 137 ```graphql 138 directive @constraint( 139 format: String 140 ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 141 142 input ObjectInput { 143 contactEmail: String @constraint(format: "email") 144 website: String @constraint(format: "uri") 145 } 146 ``` 147 148 Will generate the model: 149 150 ```go 151 type ObjectInput struct { 152 contactEmail *string `json:"contactEmail" validate:"email"` 153 website *string `json:"website" validate:"uri"` 154 } 155 ``` 156 157 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 158 159 ```yaml 160 directives: 161 constraint: 162 skip_runtime: true 163 ``` 164 165 The built-in directive `@goTag` is implemented using the FieldMutateHook. See: `plugin/modelgen/models.go` function `GoTagFieldHook`