diff --git a/query.go b/query.go index 5d47c79..22cca90 100644 --- a/query.go +++ b/query.go @@ -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]". @@ -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) } @@ -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 diff --git a/query_test.go b/query_test.go index 4e9d72b..56e951d 100644 --- a/query_test.go +++ b/query_test.go @@ -5,6 +5,8 @@ import ( "net/url" "testing" "time" + + "github.com/google/uuid" ) type cachedDirective struct { @@ -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. { @@ -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) @@ -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. diff --git a/type.go b/type.go new file mode 100644 index 0000000..c5e9138 --- /dev/null +++ b/type.go @@ -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 +}