github.com/mstephano/gqlgen-schemagen@v0.0.0-20230113041936-dd2cd4ea46aa/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/mstephano/gqlgen-schemagen/api" 25 "github.com/mstephano/gqlgen-schemagen/codegen/config" 26 "github.com/mstephano/gqlgen-schemagen/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/mstephano/gqlgen-schemagen/api" 94 "github.com/mstephano/gqlgen-schemagen/codegen/config" 95 "github.com/mstephano/gqlgen-schemagen/plugin/modelgen" 96 ) 97 98 // Defining mutation function 99 func constraintFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *modelgen.Field) (*modelgen.Field, error) { 100 // Call default hook to proceed standard directives like goField and goTag. 101 // You can omit it, if you don't need. 102 if f, err := modelgen.DefaultFieldMutateHook(td, fd, f); err != nil { 103 return f, err 104 } 105 106 c := fd.Directives.ForName("constraint") 107 if c != nil { 108 formatConstraint := c.Arguments.ForName("format") 109 110 if formatConstraint != nil{ 111 f.Tag += " validate:"+formatConstraint.Value.String() 112 } 113 114 } 115 116 return f, nil 117 } 118 119 func main() { 120 cfg, err := config.LoadConfigFromDefaultLocations() 121 if err != nil { 122 fmt.Fprintln(os.Stderr, "failed to load config", err.Error()) 123 os.Exit(2) 124 } 125 126 // Attaching the mutation function onto modelgen plugin 127 p := modelgen.Plugin{ 128 FieldHook: constraintFieldHook, 129 } 130 131 err = api.Generate(cfg, api.ReplacePlugin(&p)) 132 133 if err != nil { 134 fmt.Fprintln(os.Stderr, err.Error()) 135 os.Exit(3) 136 } 137 } 138 ``` 139 140 This schema: 141 142 ```graphql 143 directive @constraint(format: String) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 144 145 input ObjectInput { 146 contactEmail: String @constraint(format: "email") 147 website: String @constraint(format: "uri") 148 } 149 ``` 150 151 Will generate the model: 152 153 ```go 154 type ObjectInput struct { 155 contactEmail *string `json:"contactEmail" validate:"email"` 156 website *string `json:"website" validate:"uri"` 157 } 158 ``` 159 160 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 161 162 ```yaml 163 directives: 164 constraint: 165 skip_runtime: true 166 ``` 167 168 The built-in directives `@goField` and `@goTag` is implemented using the FieldMutateHook. See: `plugin/modelgen/models.go` functions `GoFieldHook` and `GoTagFieldHook`