Skip to content

Commit

Permalink
add scalar example
Browse files Browse the repository at this point in the history
  • Loading branch information
vektah committed Feb 16, 2018
1 parent 83b001a commit 4722a85
Show file tree
Hide file tree
Showing 8 changed files with 961 additions and 0 deletions.
686 changes: 686 additions & 0 deletions example/scalars/generated.go

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions example/scalars/model.go
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
}
60 changes: 60 additions & 0 deletions example/scalars/readme.md
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.
47 changes: 47 additions & 0 deletions example/scalars/resolvers.go
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
}
49 changes: 49 additions & 0 deletions example/scalars/scalar_test.go
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)
})
}
23 changes: 23 additions & 0 deletions example/scalars/schema.graphql
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
16 changes: 16 additions & 0 deletions example/scalars/server/server.go
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))
}
6 changes: 6 additions & 0 deletions example/scalars/types.json
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"
}

0 comments on commit 4722a85

Please sign in to comment.