I would like to add custom tags to struct; which would be read and wrote by gqlgen generator.
For example: I have input declared in graphql schema, where I wish to define sanitization, validation and probably some convert.
All I'm saying is: let us define custom tags, you don't even need to know what they are for :)
Only json:"..." tag is generated.
Lets say we have input that looks something like this:
input UserInput {
name String @input_sanitize("trim") @input_validate("not_empty")
email String! @input_sanitize("trim,lower") @input_validate("not_empty,email")
password String! @input_convert("bcrypt_hash") @input_validate("not_empty")
}
or
input UserInput {
email String! @extra_tag(input_sanitize:"trim,lower" input_validate:"not_empty,email")
...
}
and gqlgen generator would output struct that look like this:
type UserInput struct {
Name *string `input_sanitize:"trim" input_validate:"not_empty"`
Email string `input_sanitize:"trim,lower" input_validate:"not_empty,email"`
Password string `input_validate:"not_empty" input_convert:"bcrypt_hash"`
}
I know there is already a solution for modifying model struct, but for overwriting 300+ structs for only tag addition is really not a good clean solution.
We are going to address model generation the release after next when we look at plugins. Our plan is to pull model generation out into a plugin by itself that you could then add features like this to.
As you point out, the meanwhile solution would be to have these tags manually on models and map them through to gqlgen. Not ideal, but perhaps you could add another codegen step with a custom script in the meantime?
This could be great for generating Gorm compatible models (setting things like default values).
type Model struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()"`
}
+1
input UserInput {
email String! @extra_tag(input_sanitize:"trim,lower" input_validate:"not_empty,email")
...
}
model generation is now a plugin, which means you can write your own.
All thats missing is docs, and a 0.8 release.
You can now do this by replacing the model generator plugin, see https://gqlgen.com/reference/plugins/
Hi all, having reviewed the documentation for model generation as a plugin, I wasn't able to find the correct location to create custom struct tags. Would very much appreciate some help. Thank you!
Hi all, having reviewed the documentation for model generation as a plugin, I wasn't able to find the correct location to create custom struct tags. Would very much appreciate some help. Thank you!
Bump.
We are facing the same need for custom struct tags.
Is there an expected release date for 0.8? Thanks!
Hi all, having reviewed the documentation for model generation as a plugin, I wasn't able to find the correct location to create custom struct tags. Would very much appreciate some help. Thank you!
Need it too. Is this feature already released? Could anyone provide link to doc? Thanks!
We need custom tags too. Where can we find more details and the documentation?
+1 need documentation
+1 need documentation!
for anyone who also has this issue, here is a workaround I used:
models.go and models.gotplI copied both files from here to a folder called plugin into my project.
so, we're going to introduce a new directive on our fields, you can use the following to the schema.graphql:
directive @meta(
gorm: String,
) on OBJECT | FIELD_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION | ENUM | INPUT_OBJECT | ARGUMENT_DEFINITION
type Customer {
website: String @meta(gorm: "VARCHAR(255)")
}
models.goField struct should then look like this:type Field struct {
Description string
Name string
Type types.Type
Tag string
Gorm string // this is what you are looking for
}
func (m *Plugin) Name() string {
return "mycustommodelgenerator"
}
if cfg.Models.UserDefined(schemaType.Name) {
continue
}
and replace it with:
if schemaType.BuiltIn {
continue
}
gorm tag. Therefore on the line 166-167 add the following:gormType := ""
directive := field.Directives.ForName("meta")
if directive != nil {
arg := directive.Arguments.ForName("gorm")
if arg != nil {
gormType = fmt.Sprintf("gorm:\"%s\"", arg.Value.Raw)
}
}
it.Fields = append(it.Fields, &Field{
Name: name,
Type: typ,
Description: field.Description,
Tag: `json:"` + field.Name + `"`,
Gorm: gormType,
})
what we did so far, was to tell the model generator that we will have a directive called meta and inside it, we have a field called gorm. So the model generator should actually print it to our gen_model.go or whatever name you gave it in gqlgen.yml. mine is:
model:
filename: database/model/gql.go
package: model
so, edit the models.gotpl you copied (I think it's between the lines 22 and 37):
{{ range $model := .Models }}
{{with .Description }} {{.|prefixLines "// "}} {{end}}
type {{ .Name|go }} struct {
{{- range $field := .Fields }}
{{- with .Description }}
{{.|prefixLines "// "}}
{{- end}}
{{ $field.Name|go }} {{$field.Type | ref}} `{{$field.Tag}} {{$field.Gorm}}`
{{- end }}
}
{{- range $iface := .Implements }}
func ({{ $model.Name|go }}) Is{{ $iface|go }}() {}
{{- end }}
{{- end}}
the part that has the {{$field.Gorm}} is where the magic happens.
we have to declare our directive to the configuration, you can take a look at here to get an idea of how it would be possible.
Here is my sample:
config.Directives.Meta = func(ctx context.Context, obj interface{}, next graphql.Resolver, gorm *string, baseModelIncluded *bool) (res interface{}, err error) {
return next(ctx)
}
What I experienced so far, the plugin gets called as soon as I run my project. So you might want to create a CLI command for your app, and there with a flag or something call the plugin. here's what I did:
bin/cli.go
package main
import (
"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
"gitlab.com/jupiter-agency/jupiter-projects/mission-control/database"
"gitlab.com/jupiter-agency/jupiter-projects/mission-control/database/model"
"gitlab.com/jupiter-agency/jupiter-projects/mission-control/util"
"gitlab.com/jupiter-agency/jupiter-projects/mission-control/util/plugin"
"log"
"os"
"reflect"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Commands: []*cli.Command{
{
Name: "generate",
Usage: "generate graphql schema",
Action: func(c *cli.Context) error {
gqlgenConf, err := config.LoadConfigFromDefaultLocations()
if err != nil {
util.Log.Panicln("failed to load config", err.Error())
os.Exit(2)
}
util.Log.Infoln("generating schema...")
err = api.Generate(gqlgenConf,
api.AddPlugin(plugin.New()), // This is the magic line
)
util.Log.Infoln("schema generation done...")
if err != nil {
util.Log.Panicln(err.Error())
os.Exit(3)
}
os.Exit(0)
return nil
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
let me know if you encounter any bugs.
Another example of this for people who find this issue and do not find the proper documentation is here: https://gqlgen.com/recipes/modelgen-hook/
For the simple use case using the code located at the gqlgen docs linked above should solve the issue. For some of the more complex use cases you could expand this to accept directives and do something similar to what AienTech has done above. I like using the method in the docs because it adjusts the models at generation time.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Nope, stale!
Please reopen, guys.
Most helpful comment
Hi all, having reviewed the documentation for model generation as a plugin, I wasn't able to find the correct location to create custom struct tags. Would very much appreciate some help. Thank you!