Skip to content

Commit

Permalink
Allow specifying GraphQL type for a particular type (shurcooL#33)
Browse files Browse the repository at this point in the history
Co-authored-by: Nizar Malangadan <[email protected]>
  • Loading branch information
nizar-m and nizar-m authored Apr 30, 2022
1 parent 3d809b3 commit 413dfb6
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 2 deletions.
16 changes: 16 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ func writeArgumentType(w io.Writer, t reflect.Type, value bool) {
return
}

if t.Implements(graphqlTypeInterface) {
graphqlType, ok := reflect.Zero(t).Interface().(GraphQLType)
if ok {
io.WriteString(w, graphqlType.GetGraphQLType())
if value {
// Value is a required type, so add "!" to the end.
io.WriteString(w, "!")
}
return
}
}

switch t.Kind() {
case reflect.Slice, reflect.Array:
// List. E.g., "[Int]".
Expand All @@ -141,9 +153,11 @@ func writeArgumentType(w io.Writer, t reflect.Type, value bool) {
default:
// Named type. E.g., "Int".
name := t.Name()

if name == "string" { // HACK: Workaround for https://github.com/shurcooL/githubv4/issues/12.
name = "ID"
}

io.WriteString(w, name)
}

Expand Down Expand Up @@ -257,6 +271,8 @@ func FieldSafe(valStruct reflect.Value, i int) reflect.Value {

var jsonUnmarshaler = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()

var graphqlTypeInterface = reflect.TypeOf((*GraphQLType)(nil)).Elem()

func isTrue(s string) bool {
b, _ := strconv.ParseBool(s)
return b
Expand Down
40 changes: 38 additions & 2 deletions query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"net/url"
"testing"
"time"

"github.com/google/uuid"
)

type cachedDirective struct {
Expand Down Expand Up @@ -582,14 +584,15 @@ func TestConstructSubscription(t *testing.T) {
} `graphql:"users(first:10)"`
}
} `graphql:"issue(number: $issueNumber)"`
} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName, review: $userReview)"`
}{},
inVariables: map[string]interface{}{
"repositoryOwner": String("shurcooL-test"),
"repositoryName": String("test-repo"),
"issueNumber": Int(1),
"review": UserReview{},
},
want: `subscription SearchRepository($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!){repository(owner: $repositoryOwner, name: $repositoryName){issue(number: $issueNumber){reactionGroups{users(first:10){nodes{login}}}}}}`,
want: `subscription SearchRepository($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!$review:user_review!){repository(owner: $repositoryOwner, name: $repositoryName, review: $userReview){issue(number: $issueNumber){reactionGroups{users(first:10){nodes{login}}}}}}`,
},
// Embedded structs without graphql tag should be inlined in query.
{
Expand Down Expand Up @@ -683,6 +686,18 @@ func TestQueryArguments(t *testing.T) {
in: map[string]interface{}{"ids": &[]ID{"someID", "anotherID"}},
want: `$ids:[ID!]`,
},
{
in: map[string]interface{}{
"id": Uuid(uuid.New()),
"id_optional": &val,
"ids": []Uuid{},
"ids_optional": []*Uuid{},
"my_uuid": MyUuid(uuid.New()),
"review": UserReview{},
"review_input": UserReviewInput{},
},
want: `$id:uuid!$id_optional:uuid$ids:[uuid!]!$ids_optional:[uuid]!$my_uuid:my_uuid!$review:user_review!$review_input:user_review_input!`,
},
}
for i, tc := range tests {
got := queryArguments(tc.in)
Expand All @@ -692,6 +707,27 @@ func TestQueryArguments(t *testing.T) {
}
}

var val Uuid

type Uuid uuid.UUID

func (u Uuid) GetGraphQLType() string { return "uuid" }

type MyUuid Uuid

type UserReview struct {
Review String
UserID String
}

type UserReviewInput UserReview

func (u UserReview) GetGraphQLType() string { return "user_review" }

func (u UserReviewInput) GetGraphQLType() string { return "user_review_input" }

func (u MyUuid) GetGraphQLType() string { return "my_uuid" }

// Custom GraphQL types for testing.
type (
// DateTime is an ISO-8601 encoded UTC date.
Expand Down
15 changes: 15 additions & 0 deletions type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package graphql

// GraphQLType interface is used to specify the GraphQL type associated
// with a particular type. If a type implements this interface, the name of
// the variable used while creating the GraphQL query will be the output of
// the function defined below.
//
// In the current implementation, the GetGraphQLType function is applied to
// the zero value of the type to get the GraphQL type. So those who are
// implementing the function should avoid referencing the value of the type
// inside the function. Further, by this design, the output of the GetGraphQLType
// function will be a constant.
type GraphQLType interface {
GetGraphQLType() string
}

0 comments on commit 413dfb6

Please sign in to comment.