Skip to content

Commit

Permalink
Merge pull request #897 from mskrip/modelgen-hook
Browse files Browse the repository at this point in the history
Add possibility to hook into modelgen plugin
  • Loading branch information
vektah committed Nov 11, 2019
2 parents c1e6414 + 2a269dd commit 9cfd817
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 16 deletions.
79 changes: 79 additions & 0 deletions docs/content/recipes/modelgen-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: "Allowing mutation of generated models before rendering"
description: How to use a model mutation function to insert a ORM-specific tags onto struct fields.
linkTitle: "Modelgen hook"
menu: { main: { parent: 'recipes' } }
---

The following recipe shows how to use a `modelgen` plugin hook to mutate generated
models before they are rendered into a resulting file. This feature has many uses but
the example focuses only on inserting ORM-specific tags into generated struct fields. This
is a common use case since it allows for better field matching of DB queries and
the generated data structure.

First of all, we need to create a function that will mutate the generated model.
Then we can attach the function to the plugin and use it like any other plugin.

``` go
import (
"fmt"
"os"

"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin/modelgen"
)

// Defining mutation function
func mutateHook(b *ModelBuild) *ModelBuild {
for _, model := range b.Models {
for _, field := range model.Fields {
field.Tag += ` orm_binding:"` + model.Name + `.` + field.Name + `"`
}
}

return b
}

func main() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
os.Exit(2)
}

// Attaching the mutation function onto modelgen plugin
p := modelgen.Plugin{
MutateHook: mutateHook,
}

err = api.Generate(cfg,
api.NoPlugins(),
api.AddPlugin(p),
)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(3)
}
}
```

Now fields from generated models will contain a additional tag `orm_binding`.

This schema:

```graphql
type Object {
field1: String
field2: Int
}
```

Will gen generated into:

```go
type Object struct {
field1 *string `json:"field1" orm_binding:"Object.field1"`
field2 *int `json:"field2" orm_binding:"Object.field2"`
}
```
18 changes: 16 additions & 2 deletions plugin/modelgen/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import (
"github.com/vektah/gqlparser/ast"
)

type BuildMutateHook = func(b *ModelBuild) *ModelBuild

func defaultBuildMutateHook(b *ModelBuild) *ModelBuild {
return b
}

type ModelBuild struct {
PackageName string
Interfaces []*Interface
Expand Down Expand Up @@ -50,10 +56,14 @@ type EnumValue struct {
}

func New() plugin.Plugin {
return &Plugin{}
return &Plugin{
MutateHook: defaultBuildMutateHook,
}
}

type Plugin struct{}
type Plugin struct {
MutateHook BuildMutateHook
}

var _ plugin.ConfigMutator = &Plugin{}

Expand Down Expand Up @@ -221,6 +231,10 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
return nil
}

if m.MutateHook != nil {
b = m.MutateHook(b)
}

return templates.Render(templates.Options{
PackageName: cfg.Model.Package,
Filename: cfg.Model.Filename,
Expand Down
32 changes: 31 additions & 1 deletion plugin/modelgen/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
func TestModelGeneration(t *testing.T) {
cfg, err := config.LoadConfig("testdata/gqlgen.yml")
require.NoError(t, err)
p := Plugin{}
p := Plugin{
MutateHook: mutateHook,
}
require.NoError(t, p.MutateConfig(cfg))

require.True(t, cfg.Models.UserDefined("MissingTypeNotNull"))
Expand Down Expand Up @@ -42,4 +44,32 @@ func TestModelGeneration(t *testing.T) {
require.True(t, len(words) > 1, "expected description %q to have more than one word", text)
}
})

t.Run("tags are applied", func(t *testing.T) {
file, err := ioutil.ReadFile("./out/generated.go")
require.NoError(t, err)

fileText := string(file)

expectedTags := []string{
`json:"missing2" database:"MissingTypeNotNullmissing2"`,
`json:"name" database:"MissingInputname"`,
`json:"missing2" database:"MissingTypeNullablemissing2"`,
`json:"name" database:"TypeWithDescriptionname"`,
}

for _, tag := range expectedTags {
require.True(t, strings.Contains(fileText, tag))
}
})
}

func mutateHook(b *ModelBuild) *ModelBuild {
for _, model := range b.Models {
for _, field := range model.Fields {
field.Tag += ` database:"` + model.Name + field.Name + `"`
}
}

return b
}
26 changes: 13 additions & 13 deletions plugin/modelgen/out/generated.go

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

0 comments on commit 9cfd817

Please sign in to comment.