Skip to content

Commit 858d929

Browse files
authored
fix: interface objects (#1055)
fixes ENG-6082 fixes ENG-6084 fixes ENG-6086 fixes ENG-6094 fixes ENG-6095 fixes ENG-6096 fixes ENG-6097
2 parents d49a6f3 + 2a92e4b commit 858d929

25 files changed

Lines changed: 3691 additions & 405 deletions

execution/engine/testdata/complex_nesting_query_with_art.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"raw_input_data": {},
9797
"input": {
9898
"body": {
99-
"query": "{me {id username history {__typename ... on Store {location} ... on Purchase {wallet {currency}} ... on Sale {location product {upc __typename}}} __typename}}"
99+
"query": "{me {id username history {__typename ... on Sale {location product {upc __typename}} ... on Purchase {wallet {currency}}} __typename}}"
100100
},
101101
"header": {},
102102
"method": "POST",
@@ -409,7 +409,7 @@
409409
},
410410
"input": {
411411
"body": {
412-
"query": "query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename reviews {__typename attachments {__typename ... on Comment {upc __typename ... on Rating {body}} ... on Question {body} ... on Video {upc size}}}}}}",
412+
"query": "query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename reviews {__typename attachments {__typename ... on Question {upc body} ... on Rating {upc body} ... on Video {upc size}}}}}}",
413413
"variables": {
414414
"representations": [
415415
{
@@ -433,8 +433,8 @@
433433
"__typename": "Review",
434434
"attachments": [
435435
{
436-
"upc": "top-1",
437436
"__typename": "Question",
437+
"upc": "top-1",
438438
"body": "How do I turn it on?"
439439
}
440440
]
@@ -443,8 +443,8 @@
443443
"__typename": "Review",
444444
"attachments": [
445445
{
446-
"upc": "top-2",
447446
"__typename": "Rating",
447+
"upc": "top-2",
448448
"body": "The best hat I have ever bought in my life."
449449
},
450450
{
@@ -481,13 +481,13 @@
481481
"status": "200 OK",
482482
"headers": {
483483
"Content-Length": [
484-
"395"
484+
"349"
485485
],
486486
"Content-Type": [
487487
"application/json"
488488
]
489489
},
490-
"body_size": 395
490+
"body_size": 349
491491
}
492492
}
493493
}

v2/pkg/engine/datasource/graphql_datasource/entity_interfaces_engine_config.go

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const EntityInterfacesDefinition = `
1212
interface Account {
1313
id: ID!
1414
title: String!
15+
fullTitle: String!
16+
uniqueTitle: String!
1517
locations: [Location!]
1618
age: Int!
1719
}
@@ -23,20 +25,26 @@ const EntityInterfacesDefinition = `
2325
type Admin implements Account {
2426
id: ID!
2527
title: String!
28+
fullTitle: String!
29+
uniqueTitle: String!
2630
locations: [Location!]
2731
age: Int!
2832
}
2933
3034
type Moderator implements Account {
3135
id: ID!
3236
title: String!
37+
fullTitle: String!
38+
uniqueTitle: String!
3339
locations: [Location!]
3440
age: Int!
3541
}
3642
3743
type User implements Account {
3844
id: ID!
3945
title: String!
46+
fullTitle: String!
47+
uniqueTitle: String!
4048
locations: [Location!]
4149
age: Int!
4250
}
@@ -160,6 +168,8 @@ func EntityInterfacesPlanConfiguration(t *testing.T, factory plan.PlannerFactory
160168
type Account @key(fields: "id") @interfaceObject {
161169
id: ID!
162170
locations: [Location!]
171+
title: String! @external
172+
uniqueTitle: String! @requires(fields: "title")
163173
}
164174
165175
type Location {
@@ -193,20 +203,24 @@ func EntityInterfacesPlanConfiguration(t *testing.T, factory plan.PlannerFactory
193203
&plan.DataSourceMetadata{
194204
RootNodes: []plan.TypeField{
195205
{
196-
TypeName: "Account",
197-
FieldNames: []string{"id", "locations"},
206+
TypeName: "Account",
207+
FieldNames: []string{"id", "locations", "uniqueTitle"},
208+
ExternalFieldNames: []string{"title"},
198209
},
199210
{
200-
TypeName: "User",
201-
FieldNames: []string{"id", "locations"},
211+
TypeName: "User",
212+
FieldNames: []string{"id", "locations", "uniqueTitle"},
213+
ExternalFieldNames: []string{"title"},
202214
},
203215
{
204-
TypeName: "Moderator",
205-
FieldNames: []string{"id", "locations"},
216+
TypeName: "Moderator",
217+
FieldNames: []string{"id", "locations", "uniqueTitle"},
218+
ExternalFieldNames: []string{"title"},
206219
},
207220
{
208-
TypeName: "Admin",
209-
FieldNames: []string{"id", "locations"},
221+
TypeName: "Admin",
222+
FieldNames: []string{"id", "locations", "uniqueTitle"},
223+
ExternalFieldNames: []string{"title"},
210224
},
211225
{
212226
TypeName: "Query",
@@ -244,6 +258,13 @@ func EntityInterfacesPlanConfiguration(t *testing.T, factory plan.PlannerFactory
244258
SelectionSet: "id",
245259
},
246260
},
261+
Requires: plan.FederationFieldConfigurations{
262+
{
263+
TypeName: "Account",
264+
SelectionSet: "title",
265+
FieldName: "uniqueTitle",
266+
},
267+
},
247268
},
248269
},
249270
secondCustomConfiguration,
@@ -300,6 +321,8 @@ func EntityInterfacesPlanConfiguration(t *testing.T, factory plan.PlannerFactory
300321
type Account @key(fields: "id") @interfaceObject {
301322
id: ID!
302323
age: Int!
324+
title: String! @external
325+
fullTitle: String! @requires(fields: "title")
303326
}`
304327

305328
fourthDatasourceSchemaConfiguration, err := NewSchemaConfiguration(
@@ -325,20 +348,21 @@ func EntityInterfacesPlanConfiguration(t *testing.T, factory plan.PlannerFactory
325348
&plan.DataSourceMetadata{
326349
RootNodes: []plan.TypeField{
327350
{
328-
TypeName: "Account",
329-
FieldNames: []string{"id", "age"},
351+
TypeName: "Account",
352+
FieldNames: []string{"id", "age", "fullTitle"},
353+
ExternalFieldNames: []string{"title"},
330354
},
331355
{
332356
TypeName: "User",
333-
FieldNames: []string{"id", "age"},
357+
FieldNames: []string{"id", "age", "fullTitle"},
334358
},
335359
{
336360
TypeName: "Moderator",
337-
FieldNames: []string{"id", "age"},
361+
FieldNames: []string{"id", "age", "fullTitle"},
338362
},
339363
{
340364
TypeName: "Admin",
341-
FieldNames: []string{"id", "age"},
365+
FieldNames: []string{"id", "age", "fullTitle"},
342366
},
343367
},
344368
FederationMetaData: plan.FederationMetaData{
@@ -366,6 +390,13 @@ func EntityInterfacesPlanConfiguration(t *testing.T, factory plan.PlannerFactory
366390
SelectionSet: "id",
367391
},
368392
},
393+
Requires: plan.FederationFieldConfigurations{
394+
{
395+
TypeName: "Account",
396+
SelectionSet: "title",
397+
FieldName: "fullTitle",
398+
},
399+
},
369400
},
370401
},
371402
fourthCustomConfiguration,

v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/datasource/httpclient"
3030
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/plan"
3131
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
32+
"github.com/wundergraph/graphql-go-tools/v2/pkg/internal/quotes"
3233
"github.com/wundergraph/graphql-go-tools/v2/pkg/internal/unsafebytes"
3334
"github.com/wundergraph/graphql-go-tools/v2/pkg/lexer/literal"
3435
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
@@ -286,22 +287,45 @@ func (p *Planner[T]) Register(visitor *plan.Visitor, configuration plan.DataSour
286287
return nil
287288
}
288289

290+
func (p *Planner[T]) createInputForQuery() (input []byte) {
291+
opBytes, opVarsBytes := p.printOperation()
292+
upstreamVariables := p.upstreamVariables
293+
294+
if opVarsBytes != nil {
295+
err := jsonparser.ObjectEach(opVarsBytes, func(key, value []byte, dataType jsonparser.ValueType, offset int) (err error) {
296+
if dataType == jsonparser.String {
297+
upstreamVariables, err = sjson.SetRawBytes(upstreamVariables, string(key), quotes.WrapBytes(value))
298+
} else {
299+
upstreamVariables, err = sjson.SetRawBytes(upstreamVariables, string(key), value)
300+
}
301+
return err
302+
})
303+
if err != nil {
304+
p.stopWithError(errors.WithStack(fmt.Errorf("createInputForQuery: failed to copy additional variables: %w", err)))
305+
return nil
306+
}
307+
}
308+
309+
input = httpclient.SetInputBodyWithPath(input, upstreamVariables, "variables")
310+
input = httpclient.SetInputBodyWithPath(input, opBytes, "query")
311+
312+
return input
313+
}
314+
289315
func (p *Planner[T]) ConfigureFetch() resolve.FetchConfiguration {
290316
if p.config.fetch == nil {
291317
p.stopWithError(errors.WithStack(errors.New("ConfigureFetch: fetch configuration is empty")))
292318
return resolve.FetchConfiguration{}
293319
}
294320

295-
var input []byte
296-
input = httpclient.SetInputBodyWithPath(input, p.upstreamVariables, "variables")
297-
input = httpclient.SetInputBodyWithPath(input, p.printOperation(), "query")
321+
input := p.createInputForQuery()
298322

299323
header, err := json.Marshal(p.config.fetch.Header)
300324
if err != nil {
301325
p.stopWithError(errors.WithStack(fmt.Errorf("ConfigureFetch: failed to marshal header: %w", err)))
302326
return resolve.FetchConfiguration{}
303327
}
304-
if err == nil && len(header) != 0 && !bytes.Equal(header, literal.NULL) {
328+
if len(header) != 0 && !bytes.Equal(header, literal.NULL) {
305329
input = httpclient.SetInputHeader(input, header)
306330
}
307331

@@ -352,8 +376,8 @@ func (p *Planner[T]) ConfigureSubscription() plan.SubscriptionConfiguration {
352376
return plan.SubscriptionConfiguration{}
353377
}
354378

355-
input := httpclient.SetInputBodyWithPath(nil, p.upstreamVariables, "variables")
356-
input = httpclient.SetInputBodyWithPath(input, p.printOperation(), "query")
379+
input := p.createInputForQuery()
380+
357381
input = httpclient.SetInputURL(input, []byte(p.config.subscription.URL))
358382
if p.config.subscription.UseSSE {
359383
input = httpclient.SetInputFlag(input, httpclient.USE_SSE)
@@ -1374,7 +1398,7 @@ func (p *Planner[T]) generateQueryPlansForFetchConfiguration(operation *ast.Docu
13741398
}
13751399

13761400
// printOperation - prints normalized upstream operation
1377-
func (p *Planner[T]) printOperation() []byte {
1401+
func (p *Planner[T]) printOperation() (operationBytes []byte, variablesBytes []byte) {
13781402

13791403
kit := p.getKit()
13801404
defer p.releaseKit(kit)
@@ -1383,7 +1407,7 @@ func (p *Planner[T]) printOperation() []byte {
13831407
definition, err := p.config.UpstreamSchema()
13841408
if err != nil {
13851409
p.stopWithError(errors.WithStack(fmt.Errorf("printOperation: failed to read upstream schema: %w", err)))
1386-
return nil
1410+
return nil, nil
13871411
}
13881412

13891413
// When datasource is nested and definition query type do not contain operation field
@@ -1394,13 +1418,18 @@ func (p *Planner[T]) printOperation() []byte {
13941418
kit.normalizer.NormalizeOperation(p.upstreamOperation, definition, kit.report)
13951419
if kit.report.HasErrors() {
13961420
p.stopWithError(errors.WithStack(fmt.Errorf("printOperation: normalization failed: %w", kit.report)))
1397-
return nil
1421+
return nil, nil
1422+
}
1423+
1424+
if len(p.upstreamOperation.Input.Variables) > 0 {
1425+
variablesBytes = make([]byte, len(p.upstreamOperation.Input.Variables))
1426+
copy(variablesBytes, p.upstreamOperation.Input.Variables)
13981427
}
13991428

14001429
kit.validator.Validate(p.upstreamOperation, definition, kit.report)
14011430
if kit.report.HasErrors() {
14021431
p.stopWithError(errors.WithStack(fmt.Errorf("printOperation: validation failed: %w", kit.report)))
1403-
return nil
1432+
return nil, nil
14041433
}
14051434

14061435
p.generateQueryPlansForFetchConfiguration(p.upstreamOperation)
@@ -1410,7 +1439,7 @@ func (p *Planner[T]) printOperation() []byte {
14101439
err = kit.printer.Print(p.upstreamOperation, kit.buf)
14111440
if err != nil {
14121441
p.stopWithError(errors.WithStack(fmt.Errorf("printOperation: failed to print: %w", err)))
1413-
return nil
1442+
return nil, nil
14141443
}
14151444

14161445
rawOperationBytes := make([]byte, kit.buf.Len())
@@ -1424,15 +1453,15 @@ func (p *Planner[T]) printOperation() []byte {
14241453
}, kit.buf)
14251454
if err != nil {
14261455
p.stopWithError(errors.WithStack(fmt.Errorf("printOperation: failed to minify: %w", err)))
1427-
return nil
1456+
return nil, nil
14281457
}
14291458
if madeReplacements && kit.buf.Len() < len(rawOperationBytes) {
14301459
rawOperationBytes = rawOperationBytes[:kit.buf.Len()]
14311460
copy(rawOperationBytes, kit.buf.Bytes())
14321461
}
14331462
}
14341463

1435-
return rawOperationBytes
1464+
return rawOperationBytes, variablesBytes
14361465
}
14371466

14381467
func (p *Planner[T]) stopWithError(err error) {
@@ -1623,8 +1652,7 @@ var (
16231652
printer: astprinter.NewPrinter(nil),
16241653
validator: astvalidation.DefaultOperationValidator(),
16251654
normalizer: astnormalization.NewWithOpts(
1626-
// we should not extract variables from the upstream operation as they will be lost
1627-
// cause when we are building an input we use our own variables
1655+
astnormalization.WithExtractVariables(),
16281656
astnormalization.WithRemoveFragmentDefinitions(),
16291657
astnormalization.WithRemoveUnusedVariables(),
16301658
astnormalization.WithInlineFragmentSpreads(),

0 commit comments

Comments
 (0)