Skip to content

Commit

Permalink
feat: add apollo compatibility truncate floats (#908)
Browse files Browse the repository at this point in the history
  • Loading branch information
jensneuse authored Oct 2, 2024
1 parent b7f16c3 commit f02d41a
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 4 deletions.
18 changes: 14 additions & 4 deletions v2/pkg/engine/resolve/resolvable.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type Resolvable struct {

type ResolvableOptions struct {
ApolloCompatibilityValueCompletionInExtensions bool
ApolloCompatibilityTruncateFloatValues bool
}

func NewResolvable(options ResolvableOptions) *Resolvable {
Expand Down Expand Up @@ -901,12 +902,21 @@ func (r *Resolvable) walkFloat(f *Float, value *astjson.Value) bool {
r.addNonNullableFieldError(f.Path, parent)
return r.err()
}
if value.Type() != astjson.TypeNumber {
r.marshalBuf = value.MarshalTo(r.marshalBuf[:0])
r.addError(fmt.Sprintf("Float cannot represent non-float value: \"%s\"", string(r.marshalBuf)), f.Path)
return r.err()
if !r.print {
if value.Type() != astjson.TypeNumber {
r.marshalBuf = value.MarshalTo(r.marshalBuf[:0])
r.addError(fmt.Sprintf("Float cannot represent non-float value: \"%s\"", string(r.marshalBuf)), f.Path)
return r.err()
}
}
if r.print {
if r.options.ApolloCompatibilityTruncateFloatValues {
floatValue := value.GetFloat64()
if floatValue == float64(int64(floatValue)) {
_, _ = fmt.Fprintf(r.out, "%d", int64(floatValue))
return false
}
}
r.printNode(value)
}
return false
Expand Down
95 changes: 95 additions & 0 deletions v2/pkg/engine/resolve/resolvable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,101 @@ func TestResolvable_WithTracingNotStarted(t *testing.T) {
assert.Equal(t, `{"data":{"hello":"world"},"extensions":{"trace":{"version":"1","info":null,"fetches":{"kind":"Sequence"}}}}`, out.String())
}

func TestResolveFloat(t *testing.T) {
t.Run("default behaviour", func(t *testing.T) {
res := NewResolvable(ResolvableOptions{})
ctx := NewContext(context.Background())
err := res.Init(ctx, []byte(`{"f":1.0}`), ast.OperationTypeQuery)
assert.NoError(t, err)
object := &Object{
Fields: []*Field{
{
Name: []byte("f"),
Value: &Float{
Path: []string{"f"},
},
},
},
}
out := &bytes.Buffer{}
fetchTree := Sequence()
err = res.Resolve(ctx.ctx, object, fetchTree, out)

assert.NoError(t, err)
assert.Equal(t, `{"data":{"f":1.0}}`, out.String())
})
t.Run("invalid float", func(t *testing.T) {
res := NewResolvable(ResolvableOptions{})
ctx := NewContext(context.Background())
err := res.Init(ctx, []byte(`{"f":false}`), ast.OperationTypeQuery)
assert.NoError(t, err)
object := &Object{
Fields: []*Field{
{
Name: []byte("f"),
Value: &Float{
Path: []string{"f"},
},
},
},
}
out := &bytes.Buffer{}
fetchTree := Sequence()
err = res.Resolve(ctx.ctx, object, fetchTree, out)

assert.NoError(t, err)
assert.Equal(t, `{"errors":[{"message":"Float cannot represent non-float value: \"false\"","path":["f"]}],"data":null}`, out.String())
})
t.Run("truncate float", func(t *testing.T) {
res := NewResolvable(ResolvableOptions{
ApolloCompatibilityTruncateFloatValues: true,
})
ctx := NewContext(context.Background())
err := res.Init(ctx, []byte(`{"f":1.0}`), ast.OperationTypeQuery)
assert.NoError(t, err)
object := &Object{
Fields: []*Field{
{
Name: []byte("f"),
Value: &Float{
Path: []string{"f"},
},
},
},
}
out := &bytes.Buffer{}
fetchTree := Sequence()
err = res.Resolve(ctx.ctx, object, fetchTree, out)

assert.NoError(t, err)
assert.Equal(t, `{"data":{"f":1}}`, out.String())
})
t.Run("truncate float with decimal place", func(t *testing.T) {
res := NewResolvable(ResolvableOptions{
ApolloCompatibilityTruncateFloatValues: true,
})
ctx := NewContext(context.Background())
err := res.Init(ctx, []byte(`{"f":1.1}`), ast.OperationTypeQuery)
assert.NoError(t, err)
object := &Object{
Fields: []*Field{
{
Name: []byte("f"),
Value: &Float{
Path: []string{"f"},
},
},
},
}
out := &bytes.Buffer{}
fetchTree := Sequence()
err = res.Resolve(ctx.ctx, object, fetchTree, out)

assert.NoError(t, err)
assert.Equal(t, `{"data":{"f":1.1}}`, out.String())
})
}

func TestResolvable_ValueCompletion(t *testing.T) {
res := NewResolvable(ResolvableOptions{
ApolloCompatibilityValueCompletionInExtensions: true,
Expand Down

0 comments on commit f02d41a

Please sign in to comment.