Skip to content

Commit 04aa634

Browse files
committed
expose packer.Unmarshaler interface as graphql.Unmarshaler
- add tests for graphql.Time as reference implementation
1 parent c46cc71 commit 04aa634

File tree

4 files changed

+172
-0
lines changed

4 files changed

+172
-0
lines changed

graphql.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/graph-gophers/graphql-go/errors"
1111
"github.com/graph-gophers/graphql-go/internal/common"
1212
"github.com/graph-gophers/graphql-go/internal/exec"
13+
"github.com/graph-gophers/graphql-go/internal/exec/packer"
1314
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
1415
"github.com/graph-gophers/graphql-go/internal/exec/selected"
1516
"github.com/graph-gophers/graphql-go/internal/query"
@@ -20,6 +21,9 @@ import (
2021
"github.com/graph-gophers/graphql-go/trace"
2122
)
2223

24+
// Unmarshaler defines the public api of Go types mapped to custom GraphQL scalar types
25+
type Unmarshaler = packer.Unmarshaler
26+
2327
// ParseSchema parses a GraphQL schema and attaches the given root resolver. It returns an error if
2428
// the Go type signature of the resolvers does not match the schema. If nil is passed as the
2529
// resolver, then the schema can not be executed, but it may be inspected (e.g. with ToJSON).

internal/exec/packer/packer.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,13 @@ func (p *unmarshalerPacker) Pack(value interface{}) (reflect.Value, error) {
330330
}
331331

332332
type Unmarshaler interface {
333+
// ImplementsGraphQLType maps the implementing custom Go type
334+
// to the GraphQL scalar type in the schema.
333335
ImplementsGraphQLType(name string) bool
336+
// UnmarshalGraphQL is the custom unmarshaler for the implementing type
337+
//
338+
// This function will be called whenever you use the
339+
// custom GraphQL scalar type as an input
334340
UnmarshalGraphQL(input interface{}) error
335341
}
336342

time.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ func (t *Time) UnmarshalGraphQL(input interface{}) error {
3131
var err error
3232
t.Time, err = time.Parse(time.RFC3339, input)
3333
return err
34+
case []byte:
35+
var err error
36+
t.Time, err = time.Parse(time.RFC3339, string(input))
37+
return err
3438
case int32:
3539
t.Time = time.Unix(int64(input), 0)
3640
return nil

time_test.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package graphql_test
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
"time"
8+
9+
. "github.com/graph-gophers/graphql-go"
10+
)
11+
12+
func TestTime_ImplementsUnmarshaler(t *testing.T) {
13+
defer func() {
14+
if err := recover(); err != nil {
15+
t.Error(err)
16+
}
17+
}()
18+
19+
// assert *Time implements Unmarshaler interface
20+
var _ Unmarshaler = (*Time)(nil)
21+
}
22+
23+
func TestTime_ImplementsGraphQLType(t *testing.T) {
24+
gt := new(Time)
25+
26+
if gt.ImplementsGraphQLType("foobar") {
27+
t.Error("Type *Time must not claim to implement GraphQL type 'foobar'")
28+
}
29+
30+
if !gt.ImplementsGraphQLType("Time") {
31+
t.Error("Failed asserting *Time implements GraphQL type Time")
32+
}
33+
}
34+
35+
func TestTime_MarshalJSON(t *testing.T) {
36+
var err error
37+
var b1, b2 []byte
38+
ref := time.Date(2021, time.April, 20, 12, 3, 23, 0, time.UTC)
39+
40+
if b1, err = json.Marshal(ref); err != nil {
41+
t.Error(err)
42+
return
43+
}
44+
45+
if b2, err = json.Marshal(Time{Time: ref}); err != nil {
46+
t.Errorf("MarshalJSON() error = %v", err)
47+
return
48+
}
49+
50+
if !bytes.Equal(b1, b2) {
51+
t.Errorf("MarshalJSON() got = %s, want = %s", b2, b1)
52+
}
53+
}
54+
55+
func TestTime_UnmarshalGraphQL(t *testing.T) {
56+
type args struct {
57+
input interface{}
58+
}
59+
60+
ref := time.Date(2021, time.April, 20, 12, 3, 23, 0, time.UTC)
61+
62+
t.Run("invalid", func(t *testing.T) {
63+
tests := []struct {
64+
name string
65+
args args
66+
wantErr string
67+
}{
68+
{
69+
name: "boolean",
70+
args: args{input: true},
71+
wantErr: "wrong type for Time: bool",
72+
},
73+
{
74+
name: "invalid format",
75+
args: args{input: ref.Format(time.ANSIC)},
76+
wantErr: `parsing time "Tue Apr 20 12:03:23 2021" as "2006-01-02T15:04:05Z07:00": cannot parse "Tue Apr 20 12:03:23 2021" as "2006"`,
77+
},
78+
}
79+
80+
for _, tt := range tests {
81+
t.Run(tt.name, func(t *testing.T) {
82+
gt := new(Time)
83+
if err := gt.UnmarshalGraphQL(tt.args.input); err != nil {
84+
if err.Error() != tt.wantErr {
85+
t.Errorf("UnmarshalGraphQL() error = %v, want = %s", err, tt.wantErr)
86+
}
87+
88+
return
89+
}
90+
91+
t.Error("UnmarshalGraphQL() expected error not raised")
92+
})
93+
}
94+
})
95+
96+
tests := []struct {
97+
name string
98+
args args
99+
wantEq time.Time
100+
}{
101+
{
102+
name: "time.Time",
103+
args: args{
104+
input: ref,
105+
},
106+
wantEq: ref,
107+
},
108+
{
109+
name: "string",
110+
args: args{
111+
input: ref.Format(time.RFC3339),
112+
},
113+
wantEq: ref,
114+
},
115+
{
116+
name: "bytes",
117+
args: args{
118+
input: []byte(ref.Format(time.RFC3339)),
119+
},
120+
wantEq: ref,
121+
},
122+
{
123+
name: "int32",
124+
args: args{
125+
input: int32(ref.Unix()),
126+
},
127+
wantEq: ref,
128+
},
129+
{
130+
name: "int64",
131+
args: args{
132+
input: ref.Unix(),
133+
},
134+
wantEq: ref,
135+
},
136+
{
137+
name: "float64",
138+
args: args{
139+
input: float64(ref.Unix()),
140+
},
141+
wantEq: ref,
142+
},
143+
}
144+
145+
for _, tt := range tests {
146+
t.Run(tt.name, func(t *testing.T) {
147+
gt := new(Time)
148+
if err := gt.UnmarshalGraphQL(tt.args.input); err != nil {
149+
t.Errorf("UnmarshalGraphQL() error = %v", err)
150+
return
151+
}
152+
153+
if !gt.Equal(tt.wantEq) {
154+
t.Errorf("UnmarshalGraphQL() got = %v, want = %v", gt, tt.wantEq)
155+
}
156+
})
157+
}
158+
}

0 commit comments

Comments
 (0)