Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
57e87ab
feat: add option to send why fields where included in the request
ysmolski Aug 21, 2025
f04bc09
fix style and comments
ysmolski Aug 21, 2025
93b813a
rename fields
ysmolski Aug 22, 2025
9a34074
use post method in the test
ysmolski Aug 22, 2025
72b8aff
Merge branch 'master' into yury/eng-7769-fields-authorization-for-sub…
ysmolski Aug 22, 2025
d96c569
use the fieldsRequestedBy name everywhere
ysmolski Aug 26, 2025
b59fef0
use protectedFields created by composition
ysmolski Aug 27, 2025
bad1066
gofmt
ysmolski Aug 27, 2025
dc11bc1
Merge branch 'master' into yury/eng-7769-fields-authorization-for-sub…
ysmolski Aug 27, 2025
17136a9
gofmt
ysmolski Aug 27, 2025
fa287d1
Merge branch 'master' into yury/eng-7769-fields-authorization-for-sub…
ysmolski Aug 27, 2025
1a4370d
use short paths in field accessors
ysmolski Aug 27, 2025
0b8a732
deduplicate and merge results
ysmolski Aug 27, 2025
0267751
Merge branch 'master' into yury/eng-7769-fields-authorization-for-sub…
ysmolski Aug 27, 2025
23526ad
fix nits
ysmolski Aug 27, 2025
cd2c802
rename the extension
ysmolski Aug 28, 2025
15f569f
Merge branch 'master' into yury/eng-7769-fields-authorization-for-sub…
ysmolski Aug 28, 2025
e77ab73
narrow the interface and sort everything too
ysmolski Aug 28, 2025
3b3c1ab
address PR comments
ysmolski Aug 28, 2025
6dfc7ea
fix a comment and a field name
ysmolski Aug 29, 2025
4d5fa62
fix typo
ysmolski Aug 29, 2025
b3a2261
rename the option in the resolve.go
ysmolski Sep 1, 2025
08ca330
Merge branch 'master' into yury/eng-7769-fields-authorization-for-sub…
ysmolski Sep 1, 2025
901dc9c
Revert "rename the option in the resolve.go"
ysmolski Sep 1, 2025
ee74209
streamline the loader/resolver option names
ysmolski Sep 1, 2025
f48b53c
rename var in tests
ysmolski Sep 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion execution/engine/config_factory_federation.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ func (f *FederationEngineConfigFactory) subgraphDataSourceConfiguration(engineCo
subscriptionUseSSE = *in.CustomGraphql.Subscription.UseSSE
}
}
// dataSourceRules := FetchURLRules(&routerEngineConfig.Headers, routerConfig.Subgraphs, subscriptionUrl)
// dataSourceRules := FetchURLRules(&routerEngineConfig.Headers, routerConfig.BySubgraphs, subscriptionUrl)
// forwardedClientHeaders, forwardedClientRegexps, err := PropagatedHeaders(dataSourceRules)
// if err != nil {
// return nil, fmt.Errorf("error parsing header rules for data source %s: %w", in.Id, err)
Expand Down
301 changes: 201 additions & 100 deletions execution/engine/execution_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ type _executionTestOptions struct {
resolvableOptions resolve.ResolvableOptions

apolloRouterCompatibilitySubrequestHTTPError bool
propagateFieldsRequestedBy bool
}

type executionTestOptions func(*_executionTestOptions)
Expand Down Expand Up @@ -262,6 +263,7 @@ func TestExecutionEngine_Execute(t *testing.T) {
MaxConcurrency: 1024,
ResolvableOptions: opts.resolvableOptions,
ApolloRouterCompatibilitySubrequestHTTPError: opts.apolloRouterCompatibilitySubrequestHTTPError,
PropagateFieldsRequestedBy: opts.propagateFieldsRequestedBy,
})
require.NoError(t, err)

Expand Down Expand Up @@ -822,6 +824,59 @@ func TestExecutionEngine_Execute(t *testing.T) {
},
))

t.Run("execute simple hero operation with propagating to subgraphs reason for fields being requested", runWithoutError(
ExecutionEngineTestCase{
schema: graphql.StarwarsSchema(t),
operation: graphql.LoadStarWarsQuery(starwars.FileSimpleHeroQuery, nil),
dataSources: []plan.DataSource{
mustGraphqlDataSourceConfiguration(t,
"id",
mustFactory(t,
testNetHttpClient(t, roundTripperTestCase{
expectedHost: "example.com",
expectedPath: "/",
expectedBody: `{"query":"{hero {name}}","extensions":{"FieldsRequestedBy":[{"__typename":"Character","field":"name","byUser":true},{"__typename":"Query","field":"hero","byUser":true}]}}`,
sendResponseBody: `{"data":{"hero":{"name":"Luke Skywalker"}}}`,
sendStatusCode: 200,
}),
),
&plan.DataSourceMetadata{
RootNodes: []plan.TypeField{
{
TypeName: "Query",
FieldNames: []string{"hero"},
ProtectedFieldNames: []string{"hero"},
},
},
ChildNodes: []plan.TypeField{
{
TypeName: "Character",
FieldNames: []string{"name"},
ProtectedFieldNames: []string{"name"},
},
},
},
mustConfiguration(t, graphql_datasource.ConfigurationInput{
Fetch: &graphql_datasource.FetchConfiguration{
URL: "https://example.com/",
Method: "POST",
},
SchemaConfiguration: mustSchemaConfig(
t,
nil,
string(graphql.StarwarsSchema(t).RawSchema()),
),
}),
),
},
fields: []plan.FieldConfiguration{},
expectedResponse: `{"data":{"hero":{"name":"Luke Skywalker"}}}`,
},
func(eto *_executionTestOptions) {
eto.propagateFieldsRequestedBy = true
},
))

t.Run("execute simple hero operation with graphql data source and empty errors list", runWithoutError(
ExecutionEngineTestCase{
schema: graphql.StarwarsSchema(t),
Expand Down Expand Up @@ -4291,117 +4346,130 @@ func TestExecutionEngine_Execute(t *testing.T) {
}
`

datasources := []plan.DataSource{
mustGraphqlDataSourceConfiguration(t,
"id-1",
mustFactory(t,
testNetHttpClient(t, roundTripperTestCase{
expectedHost: "first",
expectedPath: "/",
expectedBody: `{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}"}`,
sendResponseBody: `{"data":{"accounts":[{"__typename":"User","some":{"__typename":"User","id":"1"}},{"__typename":"Admin","some":{"__typename":"User","id":"2"}},{"__typename":"User","some":{"__typename":"User","id":"3"}}]}}`,
sendStatusCode: 200,
}),
),
&plan.DataSourceMetadata{
RootNodes: []plan.TypeField{
{
TypeName: "Query",
FieldNames: []string{"accounts"},
},
{
TypeName: "User",
FieldNames: []string{"id", "some"},
},
{
TypeName: "Admin",
FieldNames: []string{"id", "some"},
},
},
ChildNodes: []plan.TypeField{
{
TypeName: "Node",
FieldNames: []string{"id", "title", "some"},
},
},
FederationMetaData: plan.FederationMetaData{
Keys: plan.FederationFieldConfigurations{
makeDataSource := func(t *testing.T, expectRequestedBy bool) []plan.DataSource {
var expectedBody1 string
var expectedBody2 string
if !expectRequestedBy {
expectedBody1 = `{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}"}`
} else {
expectedBody1 = `{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}","extensions":{"FieldsRequestedBy":[{"__typename":"User","field":"id","bySubgraphs":["id-2"],"reasonIsKey":true},{"__typename":"User","field":"id","byUser":true},{"__typename":"Admin","field":"some","byUser":true}]}}`
}
expectedBody2 = `{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename title}}}","variables":{"representations":[{"__typename":"User","id":"1"},{"__typename":"User","id":"3"}]}}`

return []plan.DataSource{
mustGraphqlDataSourceConfiguration(t,
"id-1",
mustFactory(t,
testNetHttpClient(t, roundTripperTestCase{
expectedHost: "first",
expectedPath: "/",
expectedBody: expectedBody1,
sendResponseBody: `{"data":{"accounts":[{"__typename":"User","some":{"__typename":"User","id":"1"}},{"__typename":"Admin","some":{"__typename":"User","id":"2"}},{"__typename":"User","some":{"__typename":"User","id":"3"}}]}}`,
sendStatusCode: 200,
}),
),
&plan.DataSourceMetadata{
RootNodes: []plan.TypeField{
{
TypeName: "Query",
FieldNames: []string{"accounts"},
},
{
TypeName: "User",
SelectionSet: "id",
TypeName: "User",
FieldNames: []string{"id", "some"},
ProtectedFieldNames: []string{"id"},
},
{
TypeName: "Admin",
SelectionSet: "id",
TypeName: "Admin",
FieldNames: []string{"id", "some"},
ProtectedFieldNames: []string{"some"},
},
},
ChildNodes: []plan.TypeField{
{
TypeName: "Node",
FieldNames: []string{"id", "title", "some"},
},
},
FederationMetaData: plan.FederationMetaData{
Keys: plan.FederationFieldConfigurations{
{
TypeName: "User",
SelectionSet: "id",
},
{
TypeName: "Admin",
SelectionSet: "id",
},
},
},
},
},
mustConfiguration(t, graphql_datasource.ConfigurationInput{
Fetch: &graphql_datasource.FetchConfiguration{
URL: "https://first/",
Method: "POST",
},
SchemaConfiguration: mustSchemaConfig(
t,
&graphql_datasource.FederationConfiguration{
Enabled: true,
ServiceSDL: firstSubgraphSDL,
mustConfiguration(t, graphql_datasource.ConfigurationInput{
Fetch: &graphql_datasource.FetchConfiguration{
URL: "https://first/",
Method: "POST",
},
firstSubgraphSDL,
),
}),
),
mustGraphqlDataSourceConfiguration(t,
"id-2",
mustFactory(t,
testNetHttpClient(t, roundTripperTestCase{
expectedHost: "second",
expectedPath: "/",
expectedBody: `{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename title}}}","variables":{"representations":[{"__typename":"User","id":"1"},{"__typename":"User","id":"3"}]}}`,
sendResponseBody: `{"data":{"_entities":[{"__typename":"User","title":"User1"},{"__typename":"User","title":"User3"}]}}`,
sendStatusCode: 200,
SchemaConfiguration: mustSchemaConfig(
t,
&graphql_datasource.FederationConfiguration{
Enabled: true,
ServiceSDL: firstSubgraphSDL,
},
firstSubgraphSDL,
),
}),
),
&plan.DataSourceMetadata{
RootNodes: []plan.TypeField{
{
TypeName: "User",
FieldNames: []string{"id", "name", "title"},
},
{
TypeName: "Admin",
FieldNames: []string{"id", "adminName", "title"},
},
},
FederationMetaData: plan.FederationMetaData{
Keys: plan.FederationFieldConfigurations{
mustGraphqlDataSourceConfiguration(t,
"id-2",
mustFactory(t,
testNetHttpClient(t, roundTripperTestCase{
expectedHost: "second",
expectedPath: "/",
expectedBody: expectedBody2,
sendResponseBody: `{"data":{"_entities":[{"__typename":"User","title":"User1"},{"__typename":"User","title":"User3"}]}}`,
sendStatusCode: 200,
}),
),
&plan.DataSourceMetadata{
RootNodes: []plan.TypeField{
{
TypeName: "User",
SelectionSet: "id",
TypeName: "User",
FieldNames: []string{"id", "name", "title"},
},
{
TypeName: "Admin",
SelectionSet: "id",
TypeName: "Admin",
FieldNames: []string{"id", "adminName", "title"},
},
},
FederationMetaData: plan.FederationMetaData{
Keys: plan.FederationFieldConfigurations{
{
TypeName: "User",
SelectionSet: "id",
},
{
TypeName: "Admin",
SelectionSet: "id",
},
},
},
},
},
mustConfiguration(t, graphql_datasource.ConfigurationInput{
Fetch: &graphql_datasource.FetchConfiguration{
URL: "https://second/",
Method: "POST",
},
SchemaConfiguration: mustSchemaConfig(
t,
&graphql_datasource.FederationConfiguration{
Enabled: true,
ServiceSDL: secondSubgraphSDL,
mustConfiguration(t, graphql_datasource.ConfigurationInput{
Fetch: &graphql_datasource.FetchConfiguration{
URL: "https://second/",
Method: "POST",
},
secondSubgraphSDL,
),
}),
),
SchemaConfiguration: mustSchemaConfig(
t,
&graphql_datasource.FederationConfiguration{
Enabled: true,
ServiceSDL: secondSubgraphSDL,
},
secondSubgraphSDL,
),
}),
),
}
}

t.Run("run", runWithoutError(ExecutionEngineTestCase{
Expand Down Expand Up @@ -4432,9 +4500,46 @@ func TestExecutionEngine_Execute(t *testing.T) {
}`,
}
},
dataSources: datasources,
dataSources: makeDataSource(t, false),
expectedResponse: `{"data":{"accounts":[{"some":{"title":"User1"}},{"some":{"__typename":"User","id":"2"}},{"some":{"title":"User3"}}]}}`,
}))

t.Run("run with extension", runWithoutError(
ExecutionEngineTestCase{
schema: func(t *testing.T) *graphql.Schema {
t.Helper()
parseSchema, err := graphql.NewSchemaFromString(definition)
require.NoError(t, err)
return parseSchema
}(t),
operation: func(t *testing.T) graphql.Request {
return graphql.Request{
OperationName: "Accounts",
Query: `
query Accounts {
accounts {
... on User {
some {
title
}
}
... on Admin {
some {
__typename
id
}
}
}
}`,
}
},
dataSources: makeDataSource(t, true),
expectedResponse: `{"data":{"accounts":[{"some":{"title":"User1"}},{"some":{"__typename":"User","id":"2"}},{"some":{"title":"User3"}}]}}`,
},
func(eto *_executionTestOptions) {
eto.propagateFieldsRequestedBy = true
},
))
})
}

Expand Down Expand Up @@ -4592,12 +4697,10 @@ func BenchmarkIntrospection(b *testing.B) {

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

type benchCase struct {
engine *ExecutionEngine
writer *graphql.EngineResultWriter
}

newEngine := func() *ExecutionEngine {
engine, err := NewExecutionEngine(ctx, abstractlogger.NoopLogger, engineConf, resolve.ResolverOptions{
MaxConcurrency: 1024,
Expand Down Expand Up @@ -4649,12 +4752,10 @@ func BenchmarkIntrospection(b *testing.B) {
func BenchmarkExecutionEngine(b *testing.B) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

type benchCase struct {
engine *ExecutionEngine
writer *graphql.EngineResultWriter
}

newEngine := func() *ExecutionEngine {
schema, err := graphql.NewSchemaFromString(`type Query { hello: String}`)
require.NoError(b, err)
Expand Down
Loading
Loading