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`