-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
961 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package scalars | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/vektah/gqlgen/graphql" | ||
) | ||
|
||
type User struct { | ||
ID string | ||
Name string | ||
Location Point // custom scalar types | ||
Created time.Time // direct binding to builtin types with external Marshal/Unmarshal methods | ||
} | ||
|
||
// Point is serialized as a simple array, eg [1, 2] | ||
type Point struct { | ||
X int | ||
Y int | ||
} | ||
|
||
func (p *Point) UnmarshalGQL(v interface{}) error { | ||
pointStr, ok := v.(string) | ||
if !ok { | ||
return fmt.Errorf("points must be strings") | ||
} | ||
|
||
parts := strings.Split(pointStr, ",") | ||
|
||
if len(parts) != 2 { | ||
return fmt.Errorf("points must have 2 parts") | ||
} | ||
|
||
var err error | ||
if p.X, err = strconv.Atoi(parts[0]); err != nil { | ||
return err | ||
} | ||
if p.Y, err = strconv.Atoi(parts[1]); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// MarshalGQL implements the graphql.Marshaler interface | ||
func (p Point) MarshalGQL(w io.Writer) { | ||
fmt.Fprintf(w, `"%d,%d"`, p.X, p.Y) | ||
} | ||
|
||
// if the type referenced in types.json is a function that returns a marshaller we can use it to encode and decode | ||
// onto any existing go type. | ||
func MarshalTimestamp(t time.Time) graphql.Marshaler { | ||
return graphql.WriterFunc(func(w io.Writer) { | ||
io.WriteString(w, strconv.FormatInt(t.Unix(), 10)) | ||
}) | ||
} | ||
|
||
// Unmarshal{Typename} is only required if the scalar appears as an input. The raw values have already been decoded | ||
// from json into int/float64/bool/nil/map[string]interface/[]interface | ||
func UnmarshalTimestamp(v interface{}) (time.Time, error) { | ||
if tmpStr, ok := v.(int); ok { | ||
return time.Unix(int64(tmpStr), 0), nil | ||
} | ||
return time.Time{}, errors.New("time should be RFC3339 formatted string") | ||
} | ||
|
||
type SearchArgs struct { | ||
Location *Point | ||
CreatedAfter *time.Time | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
### Custom scalars | ||
|
||
There are two different ways to implement scalars in gqlgen, depending on your need. | ||
|
||
|
||
#### With user defined types | ||
For user defined types you can implement the graphql.Marshal and graphql.Unmarshal interfaces and they will be called, | ||
then add the type to your types.json | ||
|
||
|
||
#### With types you don't control | ||
|
||
If the type isn't owned by you (time.Time), or you want to represent it as a builtin type (string) you can implement | ||
some magic marshaling hooks. | ||
|
||
|
||
```go | ||
package mypkg | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"strings" | ||
|
||
"github.com/vektah/gqlgen/graphql" | ||
) | ||
|
||
|
||
func MarshalMyCustomBooleanScalar(b bool) graphql.Marshaler { | ||
return graphql.WriterFunc(func(w io.Writer) { | ||
if b { | ||
w.Write([]byte("true")) | ||
} else { | ||
w.Write([]byte("false")) | ||
} | ||
}) | ||
} | ||
|
||
func UnmarshalMyCustomBooleanScalar(v interface{}) (bool, error) { | ||
switch v := v.(type) { | ||
case string: | ||
return "true" == strings.ToLower(v), nil | ||
case int: | ||
return v != 0, nil | ||
case bool: | ||
return v, nil | ||
default: | ||
return false, fmt.Errorf("%T is not a bool", v) | ||
} | ||
} | ||
``` | ||
|
||
and then in types.json point to the name without the Marshal|Unmarshal in front: | ||
```json | ||
{ | ||
"MyCustomBooleanScalar": "github.com/me/mypkg.MyCustomBooleanScalar" | ||
} | ||
``` | ||
|
||
see the `graphql` package for more examples. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
//go:generate gorunpkg github.com/vektah/gqlgen -out generated.go | ||
|
||
package scalars | ||
|
||
import ( | ||
context "context" | ||
"time" | ||
) | ||
|
||
type Resolver struct { | ||
} | ||
|
||
func (r *Resolver) Query_user(ctx context.Context, id string) (*User, error) { | ||
return &User{ | ||
ID: id, | ||
Name: "Test User " + id, | ||
Created: time.Now(), | ||
Location: Point{1, 2}, | ||
}, nil | ||
} | ||
|
||
func (r *Resolver) Query_search(ctx context.Context, input SearchArgs) ([]User, error) { | ||
location := Point{1, 2} | ||
if input.Location != nil { | ||
location = *input.Location | ||
} | ||
|
||
created := time.Now() | ||
if input.CreatedAfter != nil { | ||
created = *input.CreatedAfter | ||
} | ||
|
||
return []User{ | ||
{ | ||
ID: "1", | ||
Name: "Test User 1", | ||
Created: created, | ||
Location: location, | ||
}, | ||
{ | ||
ID: "2", | ||
Name: "Test User 2", | ||
Created: created, | ||
Location: location, | ||
}, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package scalars | ||
|
||
import ( | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/vektah/gqlgen/client" | ||
"github.com/vektah/gqlgen/handler" | ||
introspection "github.com/vektah/gqlgen/neelance/introspection" | ||
) | ||
|
||
type RawUser struct { | ||
ID string | ||
Name string | ||
Created int64 | ||
Location string | ||
} | ||
|
||
func TestScalars(t *testing.T) { | ||
srv := httptest.NewServer(handler.GraphQL(NewExecutor(&Resolver{}))) | ||
c := client.New(srv.URL) | ||
|
||
var resp struct { | ||
User RawUser | ||
Search []RawUser | ||
} | ||
c.MustPost(`{ | ||
user(id:"1") { | ||
...UserData | ||
} | ||
search(input:{location:"6,66", createdAfter:666}) { | ||
...UserData | ||
} | ||
} | ||
fragment UserData on User { id name created location }`, &resp) | ||
|
||
require.Equal(t, "1,2", resp.User.Location) | ||
require.Equal(t, time.Now().Unix(), resp.User.Created) | ||
require.Equal(t, "6,66", resp.Search[0].Location) | ||
require.Equal(t, int64(666), resp.Search[0].Created) | ||
|
||
t.Run("introspection", func(t *testing.T) { | ||
// Make sure we can run the graphiql introspection query without errors | ||
var resp interface{} | ||
c.MustPost(introspection.Query, &resp) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
schema { | ||
query: Query | ||
} | ||
|
||
type Query { | ||
user(id: ID!): User | ||
search(input: SearchArgs!): [User!]! | ||
} | ||
|
||
type User { | ||
id: ID! | ||
name: String! | ||
created: Timestamp | ||
location: Point | ||
} | ||
|
||
input SearchArgs { | ||
location: Point | ||
createdAfter: Timestamp | ||
} | ||
|
||
scalar Timestamp | ||
scalar Point |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package main | ||
|
||
import ( | ||
"log" | ||
"net/http" | ||
|
||
"github.com/vektah/gqlgen/example/scalars" | ||
"github.com/vektah/gqlgen/handler" | ||
) | ||
|
||
func main() { | ||
http.Handle("/", handler.GraphiQL("Starwars", "/query")) | ||
http.Handle("/query", handler.GraphQL(scalars.NewExecutor(&scalars.Resolver{}))) | ||
|
||
log.Fatal(http.ListenAndServe(":8084", nil)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"User": "github.com/vektah/gqlgen/example/scalars.User", | ||
"Timestamp": "github.com/vektah/gqlgen/example/scalars.Timestamp", | ||
"SearchArgs": "github.com/vektah/gqlgen/example/scalars.SearchArgs", | ||
"Point": "github.com/vektah/gqlgen/example/scalars.Point" | ||
} |