Skip to content

Commit

Permalink
Add support for interfaces, part 4: getter methods
Browse files Browse the repository at this point in the history
Right now, if you make a query like `{ myInterface { field } }`, you
have to type-switch on all the possible implementations of `myInterface`
to get at `field`.  Now, we generate getter-methods (e.g. `GetField`),
to make that access easier.  Of course this only applies to shared
fields (which for now are the only ones, but once we support fragments
will no longer be).

This also includes a small change to the way we generate type-names for
interfaces: we no longer include the name of the concrete type in the
interface we propagate forward, so we generate
`MyInterfaceMyFieldMyType`, not `MyInterfaceMyImplMyFieldMyType`, in the
case where you have an interface `MyInterface` implemented by `MyImpl`
(and maybe other types) with field `myField: MyType`.  This is necessary
so the getter method returns a well-defined type, and also probably
convenient for calling code.  It will have to get a little bit more
complicated once we support fragments, where you could have two
implementing types with identically-named fields of different types, but
I think it'll be easiest to figure out how to deal with that when
implementing fragments.

While I was in the area, I added to the interface doc-comment a list of
the implementations.  (In GraphQL, we're guaranteed to know them all
assuming our schema is up to date.)

Issue: #8

Test plan:
make check

Reviewers: marksandstrom, adam, miguel
  • Loading branch information
benjaminjkraft committed Aug 23, 2021
1 parent 1821951 commit dfba92a
Show file tree
Hide file tree
Showing 9 changed files with 684 additions and 275 deletions.
39 changes: 37 additions & 2 deletions generate/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ func (g *generator) convertDefinition(
GoName: goName,
GoType: fieldGoType,
JSONName: field.Name,
GraphQLName: field.Name,
Description: field.Description,
}
}
Expand All @@ -264,19 +265,52 @@ func (g *generator) convertDefinition(
GoName: name,
Description: def.Description,
GraphQLName: def.Name,
SharedFields: make([]*goStructField, 0, len(selectionSet)),
Implementations: make([]*goStructType, len(implementationTypes)),
}
g.typeMap[name] = goType

// TODO(benkraft): This sorta-duplicates what we'll do in each
// implementation when it traverses the fields. But they'll differ
// more once we support fragments; at that point we should figure out
// how to refactor.
for _, selection := range selectionSet {
field, ok := selection.(*ast.Field)
if !ok { // fragment/interface, not a shared field
continue
}
_, fieldDirective, err := g.parsePrecedingComment(field, field.GetPosition())
if err != nil {
return nil, err
}
fieldOptions := queryOptions.merge(fieldDirective)

goField, err := g.convertField(namePrefix, field, fieldOptions, queryOptions)
if err != nil {
return nil, err
}
goType.SharedFields = append(goType.SharedFields, goField)
}

for i, implDef := range implementationTypes {
implName, implNamePrefix := g.typeName(namePrefix, implDef)
// Note for shared fields we propagate forward the interface's
// name-prefix: that is, the implementations will have fields with
// types like
// MyInterfaceMyFieldMyType
// not
// MyInterfaceMyImplMyFieldMyType
// ^^^^^^
// In particular, this means that the Go type of MyField will be
// the same across all the implementations; this is important so
// that we can write a method GetMyField() that returns it!
implName, _ := g.typeName(namePrefix, implDef)
// TODO(benkraft): In principle we should skip generating a Go
// field for __typename each of these impl-defs if you didn't
// request it (and it was automatically added by
// preprocessQueryDocument). But in practice it doesn't really
// hurt, and would be extra work to avoid, so we just leave it.
implTyp, err := g.convertDefinition(
implName, implNamePrefix, implDef, pos, selectionSet, queryOptions)
implName, namePrefix, implDef, pos, selectionSet, queryOptions)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -356,6 +390,7 @@ func (g *generator) convertField(
GoName: goName,
GoType: fieldGoType,
JSONName: field.Alias,
GraphQLName: field.Name,
Description: field.Definition.Description,
}, nil
}

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

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

Loading

0 comments on commit dfba92a

Please sign in to comment.