ent: entgql: Noder doesn't support UUID Types

Super excited to see ent support gqlgen (although I understand it’s still early on 😃)

I was giving it a spin this evening and noticed when trying to create a model using uuid.UUID as the ID type, I got a bunch of type errors in some of the generated code.

ent/node.go:51:3: cannot use c.ID (type uuid.UUID) as type int in field value
ent/node.go:86:3: cannot use u.ID (type uuid.UUID) as type int in field value
ent/node.go:190:21: cannot use id (type int) as type uuid.UUID in argument to testmodel1.ID
ent/node.go:199:17: cannot use id (type int) as type uuid.UUID in argument to testmodel2.ID
ent/pagination.go:450:18: cannot use c.ID (type uuid.UUID) as type int in field value
ent/pagination.go:669:18: cannot use u.ID (type uuid.UUID) as type int in field value

So I changed the generator based from the documentation to use a custom Field TypeInfo to TypeUUID

// +build ignore

package main

import (
	"log"

	"github.com/facebook/ent/entc"
	"github.com/facebook/ent/entc/gen"
	"github.com/facebook/ent/schema/field"
	"github.com/facebookincubator/ent-contrib/entgql"
)

func main() {
	err := entc.Generate("./schema", &gen.Config{
		Templates: entgql.AllTemplates,
		IDType: &field.TypeInfo{
			Type: field.TypeUUID,
		},
	})
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

Which got me a little bit further!

ent/node.go:180:26: cannot use id (type [16]byte) as type string in argument to strconv.Atoi

Looks like the culprit is located here. As the type of id argument in Noder is [16]byte, this wont work but I’m wondering if there’s an easy way to set this as uuid.UUID and use a Stringer interface to call Atoi?

Thanks!

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 20 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Are there any proper documentation when having mixed type int and uuid for id and want gql support? I am getting mixed type error when doing.

Pagination still using int instead of uuid.UUID. Just change Todo’s ID to uuid.UUID and run generate.

That’s solvable with the IDType configuration option:

package main

import (
	"log"
	"reflect"

	"github.com/facebook/ent/schema/field"
	"github.com/google/uuid"

	"github.com/facebook/ent/entc"
	"github.com/facebook/ent/entc/gen"
	"github.com/facebookincubator/ent-contrib/entgql"
)

func main() {
	rt := reflect.TypeOf(uuid.UUID{})

	err := entc.Generate("./schema", &gen.Config{
		Templates: entgql.AllTemplates,
		IDType: &field.TypeInfo{
			Type: field.TypeUUID,
			Ident:    rt.String(),
			PkgPath:  rt.PkgPath(),
		},
	})
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

An important point to make is that an ID used in ent is not the same as a graphql ID. In the Todo case it is the same as ent provides global uniqueness but it doesn’t have to be. Also note that in graphql an ID is just a scalar meaning some string that has no meaning outside the graphql server.

Now you can implement an ID over some go object (see here for docs) and make sure that the same go type provides a way to decode the node type so you can pass it to . WithNodeType .

Simple example:

type GUID struct {
    Type string `json:"T"`
    UUID string `json:"I"`
}

func (guid GUID) MarshalGQL(w io.Writer) {
  // turn guid into a string
  data, _ := json.Marshal(guid)
  s := base64.StdEncoding.EncodeToString(data)
  // graphql ID is a scalar which must be quoted
  io.WriteString(w, strconv.Quote(s))
}

func (guid *GUID) UnmarshalGQL(v interface{}) error {
  str, ok := v.(string)
  if !ok {
    return fmt.Errorf("enum %T must be a string", v)
  }
  // reverse whatever MarshalGQL did to fill guid.
}

Now in gqlgen.yaml bind ID scalar to GUID:

models:
  ID:
    model:
      - <full path for your GUID type>

After codegen gqlgen will use your GUID struct whenever a ID is used in graphql schema and calling .Node from GUID should be straightforward from there.

In the todo app case a Todo id is a global id as we use universal ids in both server and tests.

Thanks for reporting this guys. I’m giving it a look atm.

I’ve just tried this. It almost works!

I’m using Uuids (google/uuid) right now, and it generates the following:

// [...]
	ids, err = ix.QueryItem().
		Select(item.FieldID).
		Uuid.UUIDs(ctx)
// [...]

I’ve tracked it down to this line.

Maybe .Scan(ctx, &ids) would be a better alternative?

It seems enabling that mode just partitions off IDs into a special table by model type as GraphQL requires “truly unique” IDs (SQL auto-incrementing IDs are not this)

@tankbusta - As mentioned in the universal-ids doc, each node-table has a range of 1<<32 for its ids (in one database/shard). Hence, ids from different tables should not overlap in the same database.

Like I mentioned in the previous comment, I’ll create a PR for making the Type{Resolver/Mapper} configurable for the client - How do you plan to resolve the NodeType from a UUID field? prefix it with additional information?


In JS version of relay implement, they use base64(type + “:” + id) to provide Global ID. Can we do same thing with entgql? table name + entity’s id (can be number string/uuid string)

@giautm - Like I mentioned above, we’ll provide an API for configuring the TypeResolver, but the implementation is up to the user (should be simple to implement). I prefer the current approach, because it’s working seamlessly with node-ids, and don’t need to special handling in the client-side (or in the backend).

When using global IDs we’re able to derive the node type given its ID which is not possible when using UUIDs. As @a8m mentioned, we could provide some hook in the node template so you could provide your own custom ID -> Type mapper. Alternatively you could fork the template and modify it to fit your use case.