Gqlgen: Add BSON struct tag to generated models

Created on 18 Sep 2019  路  14Comments  路  Source: 99designs/gqlgen

What happened?

Generated models (with multi-part member names) cannot be used with MongoDB

What did you expect?

The ability to directly parse the resulting BSON from MongoDB queries into the models generated by gqlgen.
This is handy when the graphql schema exactly matches the models stored in MongoDB

Minimal graphql.schema and models to reproduce

type WithMultiPartMember {
  multi_part_member: String
}

Would currently generate...

type WithMultiPartMember struct {
    MultiPartMember *string `json:"multi_part_member"`
}

Would like it to generate...

type WithMultiPartMember struct {
    MultiPartMember *string `json:"multi_part_member"` `bson:"multi_part_member"` 
}

Possible fix

Replace plugin/modelgen/models.go:181

Tag: `json:"` + field.Name + `"`,

with

Tag:  `json:"` + field.Name + `" bson:"` + field.Name + `"`,

versions

  • gqlgen version v0.9.3-dev
  • go version 1.13
  • dep or go modules?

Can generate PR if you are happy with it

enhancement

Most helpful comment

Following the tips from @vvakame, I created a file called runner.go inside an otherwise empty folder with the following code:

package main

import (
    "fmt"
    "os"

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

func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
    for _, model := range b.Models {
        for _, field := range model.Fields {
            name := field.Name
            if name == "id" {
                name = "_id"
            }
            field.Tag += ` bson:"` + 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)
    }

    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)
    }
}

I then call it with go run runner\runner.go and it will generate my models with mongo friendly bson tags.

All 14 comments

i need this feature

What about the possibility to add options in graphql schema file like this:

input WithMultiPartMember {
  multi_part_member: String #`json:"multi_part_member" bson:"multi_part_member,omitempty"` 
}

I need to add bson omitempty for mongo-go updates with optional fields.

Another use case is this:

type Car {
  id: ID! # `json:"id" bson:"_id"`
  ...
}

Like this the _id in MongoDB is automatically well Marshaled and UnMarshaled

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.

@vektah - should I make a PR for this?

https://gqlgen.com/recipes/modelgen-hook/
models generated by modelgen plugin. and it has a hooks.
You can inject bson tag by yourself.

I think it seems convenient to write plugin settings in gqlgen.yml.

Following the tips from @vvakame, I created a file called runner.go inside an otherwise empty folder with the following code:

package main

import (
    "fmt"
    "os"

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

func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
    for _, model := range b.Models {
        for _, field := range model.Fields {
            name := field.Name
            if name == "id" {
                name = "_id"
            }
            field.Tag += ` bson:"` + 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)
    }

    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)
    }
}

I then call it with go run runner\runner.go and it will generate my models with mongo friendly bson tags.

Following the tips from @vvakame, I created a file called runner.go inside an otherwise empty folder with the following code:

package main

import (
  "fmt"
  "os"

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

func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
  for _, model := range b.Models {
      for _, field := range model.Fields {
          name := field.Name
          if name == "id" {
              name = "_id"
          }
          field.Tag += ` bson:"` + 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)
  }

  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)
  }
}

I then call it with go run runner\runner.go and it will generate my models with mongo friendly bson tags.

You demonstrated how to mannually run the hook, the thing is, how could the mutate hook run alongside with the normal gqlgen generate procedure?

This recipe here doesn't show whether we should run the code separately, as what you did. Or we could incorporate the hook into gqlgen generate.

@vvakame Please advise.

It seems the plugin runs the gqlgen generate procedure automatically along with hook.
So I just changed //go:generate go run github.com/99designs/gqlgen to //go:generate go run hooks/bson.go and go generate ./... works as expected.

@SergeyYakubov this works, thanks!

It seems the plugin runs the gqlgen generate procedure automatically along with hook.
So I just changed //go:generate go run github.com/99designs/gqlgen to //go:generate go run hooks/bson.go and go generate ./... works as expected.

I got go generate exited with an error, check gopls logs error when running go generate ./...

Can you provide your bson.go?

Thanks.

@kockok - it is exactly the same example showed above https://github.com/99designs/gqlgen/issues/865#issuecomment-573043996, which itself a slight modification of a gqlgen recipe, so your errors must be somewhere else.

@SergeyYakubov Wondering if package main matters.
The recipe didn't indicate which package to implement, or ANY package, e.g. package graph is ok.

This should be implemented as default. It is a must!

This should be a parameter in gqlgen.yml file

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lynntobing picture lynntobing  路  3Comments

cajax picture cajax  路  4Comments

imiskolee picture imiskolee  路  3Comments

jszwedko picture jszwedko  路  3Comments

cemremengu picture cemremengu  路  3Comments