Skip to content

Commit

Permalink
Merge pull request 99designs#122 from vektah/json-encoding-fixes
Browse files Browse the repository at this point in the history
Fix json string encoding
  • Loading branch information
vektah authored Jun 4, 2018
2 parents c7b9c2a + 61afd49 commit 618fc8a
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 8 deletions.
5 changes: 2 additions & 3 deletions example/todo/todo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import (
"net/http/httptest"
"testing"

"github.com/vektah/gqlgen/client"
"github.com/vektah/gqlgen/neelance/introspection"

"github.com/stretchr/testify/require"
"github.com/vektah/gqlgen/client"
"github.com/vektah/gqlgen/handler"
"github.com/vektah/gqlgen/neelance/introspection"
)

func TestTodo(t *testing.T) {
Expand Down
32 changes: 31 additions & 1 deletion graphql/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,39 @@ import (
"strconv"
)

const encodeHex = "0123456789ABCDEF"

func MarshalString(s string) Marshaler {
return WriterFunc(func(w io.Writer) {
io.WriteString(w, strconv.Quote(s))
start := 0
io.WriteString(w, `"`)

for i, c := range s {
if c < 0x20 || c == '\\' || c == '"' {
io.WriteString(w, s[start:i])

switch c {
case '\t':
io.WriteString(w, `\t`)
case '\r':
io.WriteString(w, `\r`)
case '\n':
io.WriteString(w, `\n`)
case '\\':
io.WriteString(w, `\\`)
case '"':
io.WriteString(w, `\"`)
default:
io.WriteString(w, `\u00`)
w.Write([]byte{encodeHex[c>>4], encodeHex[c&0xf]})
}

start = i + 1
}
}

io.WriteString(w, s[start:])
io.WriteString(w, `"`)
})
}
func UnmarshalString(v interface{}) (string, error) {
Expand Down
27 changes: 27 additions & 0 deletions graphql/string_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package graphql

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
)

func TestString(t *testing.T) {
assert.Equal(t, `"hello"`, doStrMarshal("hello"))
assert.Equal(t, `"he\tllo"`, doStrMarshal("he\tllo"))
assert.Equal(t, `"he\tllo"`, doStrMarshal("he llo"))
assert.Equal(t, `"he\nllo"`, doStrMarshal("he\nllo"))
assert.Equal(t, `"he\r\nllo"`, doStrMarshal("he\r\nllo"))
assert.Equal(t, `"he\\llo"`, doStrMarshal(`he\llo`))
assert.Equal(t, `"quotes\"nested\"in\"quotes\""`, doStrMarshal(`quotes"nested"in"quotes"`))
assert.Equal(t, `"\u0000"`, doStrMarshal("\u0000"))
assert.Equal(t, `"\u0000"`, doStrMarshal("\u0000"))
assert.Equal(t, "\"\U000fe4ed\"", doStrMarshal("\U000fe4ed"))
}

func doStrMarshal(s string) string {
var buf bytes.Buffer
MarshalString(s).MarshalGQL(&buf)
return buf.String()
}
5 changes: 3 additions & 2 deletions handler/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,12 @@ func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc
return
}

ctx := graphql.WithRequestContext(r.Context(), cfg.newRequestContext(doc, reqParams.Query, reqParams.Variables))
reqCtx := cfg.newRequestContext(doc, reqParams.Query, reqParams.Variables)
ctx := graphql.WithRequestContext(r.Context(), reqCtx)

defer func() {
if err := recover(); err != nil {
userErr := cfg.recover(ctx, err)
userErr := reqCtx.Recover(ctx, err)
sendErrorf(w, http.StatusUnprocessableEntity, userErr.Error())
}
}()
Expand Down
5 changes: 3 additions & 2 deletions handler/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ func (c *wsConnection) subscribe(message *operationMessage) bool {
return true
}

ctx := graphql.WithRequestContext(c.ctx, c.cfg.newRequestContext(doc, reqParams.Query, reqParams.Variables))
reqCtx := c.cfg.newRequestContext(doc, reqParams.Query, reqParams.Variables)
ctx := graphql.WithRequestContext(c.ctx, reqCtx)

if op.Type != query.Subscription {
var result *graphql.Response
Expand All @@ -176,7 +177,7 @@ func (c *wsConnection) subscribe(message *operationMessage) bool {
go func() {
defer func() {
if r := recover(); r != nil {
userErr := c.cfg.recover(ctx, r)
userErr := reqCtx.Recover(ctx, r)
c.sendError(message.ID, &errors.QueryError{Message: userErr.Error()})
}
}()
Expand Down
34 changes: 34 additions & 0 deletions test/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions test/resolvers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,28 @@ func TestInputDefaults(t *testing.T) {
require.True(t, called)
}

func TestJsonEncoding(t *testing.T) {
srv := httptest.NewServer(handler.GraphQL(MakeExecutableSchema(&testResolvers{})))
c := client.New(srv.URL)

var resp struct {
JsonEncoding string
}

err := c.Post(`{ jsonEncoding }`, &resp)
require.NoError(t, err)
require.Equal(t, "\U000fe4ed", resp.JsonEncoding)
}

type testResolvers struct {
err error
queryDate func(ctx context.Context, filter models.DateFilter) (bool, error)
}

func (r *testResolvers) Query_jsonEncoding(ctx context.Context) (string, error) {
return "\U000fe4ed", nil
}

func (r *testResolvers) Query_viewer(ctx context.Context) (*Viewer, error) {
return &Viewer{
User: &remote_api.User{"Bob"},
Expand Down
1 change: 1 addition & 0 deletions test/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Query {
path: [Element]
date(filter: DateFilter!): Boolean!
viewer: Viewer
jsonEncoding: String!
}

// this is a comment with a `backtick`

0 comments on commit 618fc8a

Please sign in to comment.