gqlgenc icon indicating copy to clipboard operation
gqlgenc copied to clipboard

use standard json.Unmarshal instead of custom

Open zreigz opened this issue 2 years ago • 5 comments

I have a very complex response from the API and the graphqljson.UnmarshalData throws errors. After changing to standard json.Unmarshal everything works fine.

zreigz avatar Jul 18 '22 14:07 zreigz

@Yamashou PTAL. Another PR from my side.

zreigz avatar Jul 18 '22 14:07 zreigz

@zreigz This way you probably can't bind the structure inlineFragment(ex: ... on Foo) If we make this change, the example GetNode will not work correctly.

Yamashou avatar Jul 19 '22 05:07 Yamashou

@Yamashou I've just run the example GetNode and it works fine. I also tested it with our graphQL API and everything works perfectly fine but with your custom unmarshallee I have errors

zreigz avatar Jul 19 '22 06:07 zreigz

@zreigz It works at least in my environment. I have written tests and the following cases fail

func TestUnmarshalGraphQL_pointerWithInlineFragment(t *testing.T) {
	t.Parallel()
	type actor struct {
		User struct {
			DatabaseID uint64
		} `graphql:"... on User"`
		Login string
	}
	type query struct {
		Author actor
		Editor *actor
	}
	var got query
	err := json.Unmarshal([]byte(`{
		"author": {
			"databaseId": 1,
			"login": "test1"
		},
		"editor": {
			"databaseId": 2,
			"login": "test2"
		}
	}`), &got)
	if err != nil {
		t.Fatal(err)
	}
	var want query
	want.Author = actor{
		User:  struct{ DatabaseID uint64 }{1},
		Login: "test1",
	}
	want.Editor = &actor{
		User:  struct{ DatabaseID uint64 }{2},
		Login: "test2",
	}

	if diff := cmp.Diff(got, want); diff != "" {
		t.Error(diff)
	}
}

func TestUnmarshalGraphQL_unexportedField(t *testing.T) {
	t.Parallel()
	type query struct {
		foo string
	}
	err := json.Unmarshal([]byte(`{"foo": "bar"}`), new(query))
	if err == nil {
		t.Fatal("got error: nil, want: non-nil")
	}
	got := err.Error()
	want := ": : struct field for \"foo\" doesn't exist in any of 1 places to unmarshal"
	if diff := cmp.Diff(got, want); diff != "" {
		t.Error(diff)
	}
}

func TestUnmarshalGraphQL_multipleValues(t *testing.T) {
	t.Parallel()
	type query struct {
		Foo string
	}
	err := json.Unmarshal([]byte(`{"foo": "bar"}{"foo": "baz"}`), new(query))
	if err == nil {
		t.Fatal("got error: nil, want: non-nil")
	}
	if got, want := err.Error(), "invalid token '{' after top-level value"; got != want {
		t.Errorf("got error: %v, want: %v", got, want)
	}
}

func TestUnmarshalGraphQL_union(t *testing.T) {
	t.Parallel()
	/*
		{
			__typename
			... on ClosedEvent {
				createdAt
				actor {login}
			}
			... on ReopenedEvent {
				createdAt
				actor {login}
			}
		}
	*/
	type actor struct{ Login string }
	type reopenedEvent struct {
		Actor     actor
		CreatedAt time.Time
	}
	type issueTimelineItem struct {
		Typename    string `graphql:"__typename"`
		ClosedEvent struct {
			Actor     actor
			UpdatedAt time.Time
		} `graphql:"... on ClosedEvent"`
		ReopenedEvent reopenedEvent `graphql:"... on ReopenedEvent"`
	}
	var got issueTimelineItem
	err := json.Unmarshal([]byte(`{
		"__typename": "ClosedEvent",
		"createdAt": "2017-06-29T04:12:01Z",
		"updatedAt": "2017-06-29T04:12:01Z",
		"actor": {
			"login": "shurcooL-test"
		}
	}`), &got)
	if err != nil {
		t.Fatal(err)
	}
	want := issueTimelineItem{
		Typename: "ClosedEvent",
		ClosedEvent: struct {
			Actor     actor
			UpdatedAt time.Time
		}{
			Actor: actor{
				Login: "shurcooL-test",
			},
			UpdatedAt: time.Unix(1498709521, 0).UTC(),
		},
		ReopenedEvent: reopenedEvent{
			Actor: actor{
				Login: "shurcooL-test",
			},
			CreatedAt: time.Unix(1498709521, 0).UTC(),
		},
	}
	if diff := cmp.Diff(got, want); diff != "" {
		t.Error(diff)
	}
}

func TestUnmarshalGraphQL_union2(t *testing.T) {
	t.Parallel()
	type SubscriptionItemFragment struct {
		ID string
	}
	type PurchaseItemFragment struct {
		ID string
	}
	type OrderFragment struct {
		SubscriptionItemOrder struct {
			SubscriptionItem SubscriptionItemFragment
		} `graphql:"... on SubscriptionItemOrder"`
		PurchaseItemFragment struct {
			PurchaseItem PurchaseItemFragment
		} `graphql:"... on PurchaseItemOrder"`
	}
	type BuyDashItemPayload struct {
		Order OrderFragment
	}
	var got BuyDashItemPayload
	resp := `
	{
		"order": {
			"subscriptionItem": {
				"id": "subscriptionItemOrderID"
			}
		}
	}
`
	err := json.Unmarshal([]byte(resp), &got)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	want := BuyDashItemPayload{Order: OrderFragment{
		SubscriptionItemOrder: struct {
			SubscriptionItem SubscriptionItemFragment
		}{
			SubscriptionItem: SubscriptionItemFragment{ID: "subscriptionItemOrderID"},
		},
		PurchaseItemFragment: struct {
			PurchaseItem PurchaseItemFragment
		}{},
	}}
	if diff := cmp.Diff(got, want); diff != "" {
		t.Error(diff)
	}
}

// Issue https://github.com/shurcooL/githubv4/issues/18.
func TestUnmarshalGraphQL_arrayInsideInlineFragment(t *testing.T) {
	t.Parallel()
	/*
		query {
			search(type: ISSUE, first: 1, query: "type:pr repo:owner/name") {
				nodes {
					... on PullRequest {
						commits(last: 1) {
							nodes {
								url
							}
						}
					}
				}
			}
		}
	*/
	type query struct {
		Search struct {
			Nodes []struct {
				PullRequest struct {
					Commits struct {
						Nodes []struct {
							URL string `graphql:"url"`
						}
					} `graphql:"commits(last: 1)"`
				} `graphql:"... on PullRequest"`
			}
		} `graphql:"search(type: ISSUE, first: 1, query: \"type:pr repo:owner/name\")"`
	}
	var got query
	err := json.Unmarshal([]byte(`{
		"search": {
			"nodes": [
				{
					"commits": {
						"nodes": [
							{
								"url": "https://example.org/commit/49e1"
							}
						]
					}
				}
			]
		}
	}`), &got)
	if err != nil {
		t.Fatal(err)
	}
	var want query
	want.Search.Nodes = make([]struct {
		PullRequest struct {
			Commits struct {
				Nodes []struct {
					URL string `graphql:"url"`
				}
			} `graphql:"commits(last: 1)"`
		} `graphql:"... on PullRequest"`
	}, 1)
	want.Search.Nodes[0].PullRequest.Commits.Nodes = make([]struct {
		URL string `graphql:"url"`
	}, 1)
	want.Search.Nodes[0].PullRequest.Commits.Nodes[0].URL = "https://example.org/commit/49e1"
	if diff := cmp.Diff(got, want); diff != "" {
		t.Error(diff)
	}
}

func TestUnmarshalGraphQL_jsonRawMessage(t *testing.T) {
	t.Parallel()
	type query struct {
		JSONBlob    json.RawMessage `json:"jsonBlob"`
		JSONArray   json.RawMessage `json:"jsonArray"`
		JSONNumber  json.RawMessage `json:"jsonNumber"`
		JSONString  json.RawMessage `json:"jsonString"`
		JSONOmmited json.RawMessage `json:"jsonOmmited"`
		Number      int             `json:"number"`
		String      string          `json:"string"`
	}

	var got query
	err := json.Unmarshal([]byte(`{
		"jsonBlob": {
			"foo": "bar"
		},
		"jsonArray": [1, "two", 3],
		"jsonNumber": 2,
		"jsonString": "json string",
		"number": 1,
		"string": "normal string"
	}`), &got)
	if err != nil {
		t.Fatal(err)
	}

	want := query{
		JSONBlob:    []byte(`{"foo":"bar"}`),
		JSONArray:   []byte(`[1,"two",3]`),
		JSONNumber:  []byte(`2`),
		JSONString:  []byte(`"json string"`),
		JSONOmmited: nil,
		Number:      1,
		String:      "normal string",
	}

	if diff := cmp.Diff(got, want); diff != "" {
		t.Error(diff)
	}
}

func TestUnmarshalGraphQL_jsonRawMessageInFragment(t *testing.T) {
	t.Parallel()
	type Object struct {
		Properties struct {
			ID       string
			Metadata json.RawMessage
		} `graphql:"... on Properties"`
		Value string
	}
	type query struct {
		Object         Object
		OptionalObject *Object
	}
	var got query
	err := json.Unmarshal([]byte(`{
		"object": {
			"id": "81beda46-02c1-4641-aa7b-09cc6634c783",
			"metadata": {
				"created": "2021-05-03T21:27:28+00:00"
			},
			"value": "object value 1"
		},
		"optionalObject": {
			"id": "6f8af214-f307-4d4d-89d3-965d8b79e3bf",
			"metadata": {
				"created": "2021-05-03T21:27:28+00:00",
				"deleted": "2021-05-04T21:27:28+00:00"
			},
			"value": "object value 2"
		}
	}`), &got)
	if err != nil {
		t.Fatal(err)
	}
	var want query
	want.Object = Object{
		Properties: struct {
			ID       string
			Metadata json.RawMessage
		}{
			ID:       "81beda46-02c1-4641-aa7b-09cc6634c783",
			Metadata: []byte(`{"created":"2021-05-03T21:27:28+00:00"}`),
		},
		Value: "object value 1",
	}
	want.OptionalObject = &Object{
		Properties: struct {
			ID       string
			Metadata json.RawMessage
		}{
			ID:       "6f8af214-f307-4d4d-89d3-965d8b79e3bf",
			Metadata: []byte(`{"created":"2021-05-03T21:27:28+00:00","deleted":"2021-05-04T21:27:28+00:00"}`),
		},
		Value: "object value 2",
	}

	if diff := cmp.Diff(got, want); diff != "" {
		t.Error(diff)
	}
}

Yamashou avatar Jul 20 '22 00:07 Yamashou

thanks for the update

zreigz avatar Jul 20 '22 06:07 zreigz