Skip to content

Commit

Permalink
fix: input template rendering (#964)
Browse files Browse the repository at this point in the history
  • Loading branch information
devsergiy authored Nov 8, 2024
2 parents 8bc4798 + beac955 commit b716695
Show file tree
Hide file tree
Showing 12 changed files with 1,669 additions and 421 deletions.
4 changes: 3 additions & 1 deletion go.work
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ use (

replace github.com/99designs/gqlgen => github.com/99designs/gqlgen v0.17.22

replace github.com/tidwall/sjson => github.com/tidwall/sjson v1.0.4
replace github.com/tidwall/sjson => github.com/tidwall/sjson v1.0.4

//replace github.com/wundergraph/astjson => ../wundergraph-projects/astjson
2 changes: 1 addition & 1 deletion v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ require (
github.com/tidwall/gjson v1.17.0
github.com/tidwall/sjson v1.2.5
github.com/vektah/gqlparser/v2 v2.5.14
github.com/wundergraph/astjson v0.0.0-20241105103047-3b2e8a2b2779
github.com/wundergraph/astjson v0.0.0-20241108124845-44485579ffa5
go.uber.org/atomic v1.11.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.26.0
Expand Down
2 changes: 2 additions & 0 deletions v2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ github.com/vektah/gqlparser/v2 v2.5.14 h1:dzLq75BJe03jjQm6n56PdH1oweB8ana42wj7E4
github.com/vektah/gqlparser/v2 v2.5.14/go.mod h1:WQQjFc+I1YIzoPvZBhUQX7waZgg3pMLi0r8KymvAE2w=
github.com/wundergraph/astjson v0.0.0-20241105103047-3b2e8a2b2779 h1:c9pa8s5eOFEOBH9Vs+VsP4EcsdcHzAaDKooHBdUmmK0=
github.com/wundergraph/astjson v0.0.0-20241105103047-3b2e8a2b2779/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
github.com/wundergraph/astjson v0.0.0-20241108124845-44485579ffa5 h1:rc+IQxG3rrAXEjBywirkzhKkyCKvXLGQXABVD8GiUtU=
github.com/wundergraph/astjson v0.0.0-20241108124845-44485579ffa5/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
Expand Down
32 changes: 8 additions & 24 deletions v2/pkg/engine/datasource/httpclient/nethttpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"os"
"slices"
"strings"
"sync"
"time"

"github.com/buger/jsonparser"
Expand Down Expand Up @@ -116,23 +115,6 @@ func respBodyReader(res *http.Response) (io.Reader, error) {
}
}

var (
requestBufferPool = &sync.Pool{
New: func() any {
return &bytes.Buffer{}
},
}
)

func getBuffer() *bytes.Buffer {
return requestBufferPool.Get().(*bytes.Buffer)
}

func releaseBuffer(buf *bytes.Buffer) {
buf.Reset()
requestBufferPool.Put(buf)
}

type bodyHashContextKey struct{}

func BodyHashFromContext(ctx context.Context) (uint64, bool) {
Expand Down Expand Up @@ -217,14 +199,16 @@ func makeHTTPRequest(client *http.Client, ctx context.Context, url, method, head
}

if !enableTrace {
if response.ContentLength > 0 {
out.Grow(int(response.ContentLength))
} else {
out.Grow(1024 * 4)
}
_, err = out.ReadFrom(respReader)
return
}

buf := getBuffer()
defer releaseBuffer(buf)

_, err = buf.ReadFrom(respReader)
data, err := io.ReadAll(respReader)
if err != nil {
return err
}
Expand All @@ -238,14 +222,14 @@ func makeHTTPRequest(client *http.Client, ctx context.Context, url, method, head
StatusCode: response.StatusCode,
Status: response.Status,
Headers: redactHeaders(response.Header),
BodySize: buf.Len(),
BodySize: len(data),
},
}
trace, err := json.Marshal(responseTrace)
if err != nil {
return err
}
responseWithTraceExtension, err := jsonparser.Set(buf.Bytes(), trace, "extensions", "trace")
responseWithTraceExtension, err := jsonparser.Set(data, trace, "extensions", "trace")
if err != nil {
return err
}
Expand Down
102 changes: 89 additions & 13 deletions v2/pkg/engine/resolve/inputtemplate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,18 +372,66 @@ func TestInputTemplate_Render(t *testing.T) {
})

t.Run("GraphQLVariableResolveRenderer", func(t *testing.T) {
t.Run("nested objects", func(t *testing.T) {
t.Run("optional fields", func(t *testing.T) {
template := InputTemplate{
Segments: []TemplateSegment{
{
SegmentType: StaticSegmentType,
Data: []byte(`{"key":`),
SegmentType: VariableSegmentType,
VariableKind: ResolvableObjectVariableKind,
Renderer: NewGraphQLVariableResolveRenderer(&Object{
Nullable: false,
Fields: []*Field{
{
Name: []byte("name"),
Value: &String{
Path: []string{"name"},
Nullable: true,
},
},
},
}),
},
},
}

data := astjson.MustParseBytes([]byte(`{"name":"foo"}`))
ctx := &Context{
ctx: context.Background(),
}
buf := &bytes.Buffer{}

err := template.Render(ctx, data, buf)
assert.NoError(t, err)
out := buf.String()
assert.Equal(t, `{"name":"foo"}`, out)

data = astjson.MustParseBytes([]byte(`{}`))
buf.Reset()
err = template.Render(ctx, data, buf)
assert.NoError(t, err)
out = buf.String()
assert.Equal(t, `{"name":null}`, out)

data = astjson.MustParseBytes([]byte(`{"name":null}`))
buf.Reset()
err = template.Render(ctx, data, buf)
assert.NoError(t, err)
out = buf.String()
assert.Equal(t, `{"name":null}`, out)

data = astjson.MustParseBytes([]byte(`{"name":123}`))
buf.Reset()
err = template.Render(ctx, data, buf)
assert.Error(t, err)
})
t.Run("nested objects", func(t *testing.T) {
template := InputTemplate{
Segments: []TemplateSegment{
{
SegmentType: VariableSegmentType,
VariableKind: ResolvableObjectVariableKind,
Renderer: NewGraphQLVariableResolveRenderer(&Object{
Nullable: false,
Nullable: true,
Fields: []*Field{
{
Name: []byte("address"),
Expand Down Expand Up @@ -422,21 +470,49 @@ func TestInputTemplate_Render(t *testing.T) {
},
}),
},
{
SegmentType: StaticSegmentType,
Data: []byte(`}`),
},
},
}
ctx := &Context{
ctx: context.Background(),
Variables: astjson.MustParseBytes([]byte(`{}`)),
}
buf := &bytes.Buffer{}
err := template.Render(ctx, astjson.MustParseBytes([]byte(`{"name":"home","address":{"zip":"00000","items":[{"name":"home","active":true}]}}`)), buf)
assert.NoError(t, err)
out := buf.String()
assert.Equal(t, `{"key":{"address":{"zip":"00000","items":[{"active":true}]}}}`, out)

cases := []struct {
name string
input string
expected string
expectErr bool
}{
{
name: "data is present",
input: `{"name":"home","address":{"zip":"00000","items":[{"name":"home","active":true}]}}`,
expected: `{"address":{"zip":"00000","items":[{"active":true}]}}`,
},
{
name: "data is missing",
input: `{"name":"home"}`,
expectErr: true,
},
{
name: "partial data",
input: `{"name":"home","address":{}}`,
expectErr: true,
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
buf := &bytes.Buffer{}
err := template.Render(ctx, astjson.MustParseBytes([]byte(c.input)), buf)
if c.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
out := buf.String()
assert.Equal(t, c.expected, out)
})
}
})
})

Expand Down
30 changes: 15 additions & 15 deletions v2/pkg/engine/resolve/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ func (l *Loader) mergeResult(fetchItem *FetchItem, res *result, items []*astjson
if res.out.Len() == 0 {
return l.renderErrorsFailedToFetch(fetchItem, res, emptyGraphQLResponse)
}
value, err := l.resolvable.parseJSONBytes(res.out.Bytes())
value, err := astjson.ParseBytesWithoutCache(res.out.Bytes())
if err != nil {
return l.renderErrorsFailedToFetch(fetchItem, res, invalidGraphQLResponse)
}
Expand Down Expand Up @@ -647,7 +647,7 @@ func (l *Loader) mergeErrors(res *result, fetchItem *FetchItem, value *astjson.V
}

// Wrap mode (default)
errorObject, err := l.resolvable.parseJSONString(l.renderSubgraphBaseError(res.ds, fetchItem.ResponsePath, failedToFetchNoReason))
errorObject, err := astjson.ParseWithoutCache(l.renderSubgraphBaseError(res.ds, fetchItem.ResponsePath, failedToFetchNoReason))
if err != nil {
return err
}
Expand Down Expand Up @@ -826,7 +826,7 @@ func (l *Loader) optionallyRewriteErrorPaths(fetchItem *FetchItem, values []*ast
newPath = append(newPath, unsafebytes.BytesToString(pathItems[j].GetStringBytes()))
}
newPathJSON, _ := json.Marshal(newPath)
pathBytes, err := l.resolvable.parseJSONBytes(newPathJSON)
pathBytes, err := astjson.ParseBytesWithoutCache(newPathJSON)
if err != nil {
continue
}
Expand All @@ -852,13 +852,13 @@ func (l *Loader) setSubgraphStatusCode(values []*astjson.Value, statusCode int)
if extensions.Type() != astjson.TypeObject {
continue
}
v, err := l.resolvable.parseJSONString(strconv.Itoa(statusCode))
v, err := astjson.ParseWithoutCache(strconv.Itoa(statusCode))
if err != nil {
continue
}
extensions.Set("statusCode", v)
} else {
v, err := l.resolvable.parseJSONString(`{"statusCode":` + strconv.Itoa(statusCode) + `}`)
v, err := astjson.ParseWithoutCache(`{"statusCode":` + strconv.Itoa(statusCode) + `}`)
if err != nil {
continue
}
Expand All @@ -883,7 +883,7 @@ func (l *Loader) renderAtPathErrorPart(path string) string {

func (l *Loader) renderErrorsFailedToFetch(fetchItem *FetchItem, res *result, reason string) error {
l.ctx.appendSubgraphError(goerrors.Join(res.err, NewSubgraphError(res.ds, fetchItem.ResponsePath, reason, res.statusCode)))
errorObject, err := l.resolvable.parseJSONString(l.renderSubgraphBaseError(res.ds, fetchItem.ResponsePath, reason))
errorObject, err := astjson.ParseWithoutCache(l.renderSubgraphBaseError(res.ds, fetchItem.ResponsePath, reason))
if err != nil {
return err
}
Expand Down Expand Up @@ -914,13 +914,13 @@ func (l *Loader) renderAuthorizationRejectedErrors(fetchItem *FetchItem, res *re
if res.ds.Name == "" {
for _, reason := range res.authorizationRejectedReasons {
if reason == "" {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Unauthorized Subgraph request%s."}`, pathPart))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Unauthorized Subgraph request%s."}`, pathPart))
if err != nil {
continue
}
astjson.AppendToArray(l.resolvable.errors, errorObject)
} else {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Unauthorized Subgraph request%s, Reason: %s."}`, pathPart, reason))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Unauthorized Subgraph request%s, Reason: %s."}`, pathPart, reason))
if err != nil {
continue
}
Expand All @@ -930,13 +930,13 @@ func (l *Loader) renderAuthorizationRejectedErrors(fetchItem *FetchItem, res *re
} else {
for _, reason := range res.authorizationRejectedReasons {
if reason == "" {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Unauthorized request to Subgraph '%s'%s."}`, res.ds.Name, pathPart))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Unauthorized request to Subgraph '%s'%s."}`, res.ds.Name, pathPart))
if err != nil {
continue
}
astjson.AppendToArray(l.resolvable.errors, errorObject)
} else {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Unauthorized request to Subgraph '%s'%s, Reason: %s."}`, res.ds.Name, pathPart, reason))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Unauthorized request to Subgraph '%s'%s, Reason: %s."}`, res.ds.Name, pathPart, reason))
if err != nil {
continue
}
Expand All @@ -952,27 +952,27 @@ func (l *Loader) renderRateLimitRejectedErrors(fetchItem *FetchItem, res *result
pathPart := l.renderAtPathErrorPart(fetchItem.ResponsePath)
if res.ds.Name == "" {
if res.rateLimitRejectedReason == "" {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph request%s."}`, pathPart))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph request%s."}`, pathPart))
if err != nil {
return err
}
astjson.AppendToArray(l.resolvable.errors, errorObject)
} else {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph request%s, Reason: %s."}`, pathPart, res.rateLimitRejectedReason))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph request%s, Reason: %s."}`, pathPart, res.rateLimitRejectedReason))
if err != nil {
return err
}
astjson.AppendToArray(l.resolvable.errors, errorObject)
}
} else {
if res.rateLimitRejectedReason == "" {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph '%s'%s."}`, res.ds.Name, pathPart))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph '%s'%s."}`, res.ds.Name, pathPart))
if err != nil {
return err
}
astjson.AppendToArray(l.resolvable.errors, errorObject)
} else {
errorObject, err := l.resolvable.parseJSONString(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph '%s'%s, Reason: %s."}`, res.ds.Name, pathPart, res.rateLimitRejectedReason))
errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"Rate limit exceeded for Subgraph '%s'%s, Reason: %s."}`, res.ds.Name, pathPart, res.rateLimitRejectedReason))
if err != nil {
return err
}
Expand Down Expand Up @@ -1570,7 +1570,7 @@ func (l *Loader) compactJSON(data []byte) ([]byte, error) {
return nil, err
}
out := dst.Bytes()
v, err := astjson.ParseBytes(out)
v, err := astjson.ParseBytesWithoutCache(out)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion v2/pkg/engine/resolve/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ func BenchmarkLoader_LoadGraphQLResponseData(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
loader.Free()
resolvable.Reset(0)
resolvable.Reset()
err := resolvable.Init(ctx, nil, ast.OperationTypeQuery)
if err != nil {
b.Fatal(err)
Expand Down
Loading

0 comments on commit b716695

Please sign in to comment.