diff --git a/router-tests/go.mod b/router-tests/go.mod index 24b9a70aeb..6cfaa47d1c 100644 --- a/router-tests/go.mod +++ b/router-tests/go.mod @@ -31,7 +31,7 @@ require ( github.com/wundergraph/cosmo/router v0.0.0-20260330183556-dc4388d100a4 github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e github.com/wundergraph/cosmo/speedtrap v0.0.0-00010101000000-000000000000 - github.com/wundergraph/graphql-go-tools/v2 v2.4.6 + github.com/wundergraph/graphql-go-tools/v2 v2.5.0 go.opentelemetry.io/otel v1.44.0 go.opentelemetry.io/otel/sdk v1.44.0 go.opentelemetry.io/otel/sdk/metric v1.44.0 diff --git a/router-tests/go.sum b/router-tests/go.sum index bb8aeb0de4..624caa4e12 100644 --- a/router-tests/go.sum +++ b/router-tests/go.sum @@ -381,8 +381,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.3.0 h1:n0ng5a1vbd8YGq1u3rMr0vPU5f6AZ1BXIiUhL1UIok8= github.com/wundergraph/go-arena v1.3.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.6 h1:MqUDusuiOGlO0Oa8etTdcVA2ySmY0XehTDq/dILelEw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.6/go.mod h1:rGG9m74sUyucfvSZ83Mjuq/6qRJetl1CVP872f/dCok= +github.com/wundergraph/graphql-go-tools/v2 v2.5.0 h1:Id21OFacHv9gaictZR+dPDYWNFF0WQXbAIE4Y81Vujo= +github.com/wundergraph/graphql-go-tools/v2 v2.5.0/go.mod h1:rGG9m74sUyucfvSZ83Mjuq/6qRJetl1CVP872f/dCok= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= diff --git a/router-tests/security/costs_test.go b/router-tests/security/costs_test.go index c9601527df..c39a1c48e6 100644 --- a/router-tests/security/costs_test.go +++ b/router-tests/security/costs_test.go @@ -221,10 +221,10 @@ func TestOperationCost(t *testing.T) { require.Contains(t, res.Body, `"data":`) require.NotContains(t, res.Body, `"errors":`) - // 50 * (employees(1) + id(0) + 1 * (role(1) + 3 * departments(1) + 5 * title(1))) - require.Equal(t, "500", res.Response.Header.Get(core.CostEstimatedHeader)) - // 10 * (employees(1) + id(0) + 1 * (role(1) + 1.2 * departments(1) + 1.4 * title(1))) - require.Equal(t, "46", res.Response.Header.Get(core.CostActualHeader)) + // 50 * (employees(1) + id(0) + 1 * (role(1) + 3 * departments(1) + 5 * title(0))) + require.Equal(t, "250", res.Response.Header.Get(core.CostEstimatedHeader)) + // 10 * (employees(1) + id(0) + 1 * (role(1) + 1.2 * departments(1) + 1.4 * title(0))) + require.Equal(t, "32", res.Response.Header.Get(core.CostActualHeader)) }) }) @@ -384,6 +384,37 @@ func TestOperationCost(t *testing.T) { }) }) + t.Run("ignore_implementing_type_weights excludes implementing-type weights on abstract fields", func(t *testing.T) { + t.Parallel() + // The RoleType interface field "employees" has no weight of its own. + // With the flag on, that implementing-type weight is skipped (Apollo behavior). + testenv.Run(t, &testenv.Config{ + ModifySecurityConfiguration: func(securityConfiguration *config.SecurityConfiguration) { + securityConfiguration.CostControl = &config.CostControl{ + Enabled: true, + Mode: config.CostControlModeMeasure, + MaxEstimatedLimit: 10000, + EstimatedListSize: 10, + ExposeHeaders: true, + IgnoreImplementingTypeWeights: true, + } + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `{ employee(id:1) { role { employees { id } } } }`, + }) + require.Contains(t, res.Body, `"data":`) + require.NotContains(t, res.Body, `"errors":`) + + // "@expensiveOp(coefficient: Int = 2 @cost(weight: 22))" should not be used! + + // employee.arg(2) + 1 * (employee(5) + 1 * (role(1) + 10 * employees(1))) + require.Equal(t, "18", res.Response.Header.Get(core.CostEstimatedHeader)) + // employee.arg(2) + 1 * (employee(5) + 1 * (role(1) + 7 * employees(1))) + require.Equal(t, "15", res.Response.Header.Get(core.CostActualHeader)) + }) + }) + t.Run("slicingArguments controls list size estimation", func(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ diff --git a/router/core/executor.go b/router/core/executor.go index 785aaadb6c..ae72771f96 100644 --- a/router/core/executor.go +++ b/router/core/executor.go @@ -260,6 +260,7 @@ func (b *ExecutorConfigurationBuilder) buildPlannerConfiguration(ctx context.Con if routerEngineCfg.CostControl != nil && routerEngineCfg.CostControl.Enabled { planConfig.ComputeCosts = true planConfig.StaticCostDefaultListSize = routerEngineCfg.CostControl.EstimatedListSize + planConfig.IgnoreImplementingTypeWeights = routerEngineCfg.CostControl.IgnoreImplementingTypeWeights } return planConfig, providers, nil diff --git a/router/go.mod b/router/go.mod index 9e8bba5f64..3d81a67e86 100644 --- a/router/go.mod +++ b/router/go.mod @@ -31,7 +31,7 @@ require ( github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/twmb/franz-go v1.16.1 - github.com/wundergraph/graphql-go-tools/v2 v2.4.6 + github.com/wundergraph/graphql-go-tools/v2 v2.5.0 // Do not upgrade, it renames attributes we rely on go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 go.opentelemetry.io/contrib/propagators/b3 v1.44.0 diff --git a/router/go.sum b/router/go.sum index 9a28c67815..422f0b493c 100644 --- a/router/go.sum +++ b/router/go.sum @@ -333,8 +333,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw= github.com/wundergraph/go-arena v1.3.0 h1:n0ng5a1vbd8YGq1u3rMr0vPU5f6AZ1BXIiUhL1UIok8= github.com/wundergraph/go-arena v1.3.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.6 h1:MqUDusuiOGlO0Oa8etTdcVA2ySmY0XehTDq/dILelEw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.6/go.mod h1:rGG9m74sUyucfvSZ83Mjuq/6qRJetl1CVP872f/dCok= +github.com/wundergraph/graphql-go-tools/v2 v2.5.0 h1:Id21OFacHv9gaictZR+dPDYWNFF0WQXbAIE4Y81Vujo= +github.com/wundergraph/graphql-go-tools/v2 v2.5.0/go.mod h1:rGG9m74sUyucfvSZ83Mjuq/6qRJetl1CVP872f/dCok= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index b79707a01d..61e053dd63 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -577,6 +577,11 @@ type CostControl struct { // ExposeHeaders adds X-WG-Cost-* response headers. ExposeHeaders bool `yaml:"expose_headers,omitempty" envDefault:"false" env:"EXPOSE_HEADERS"` + + // IgnoreImplementingTypeWeights, when true, ignores @cost weights contributed by + // implementing types on abstract (interface/union) fields that have no weight of + // their own. Emulates Apollo's cost behavior. + IgnoreImplementingTypeWeights bool `yaml:"ignore_implementing_type_weights,omitempty" envDefault:"false" env:"IGNORE_IMPLEMENTING_TYPE_WEIGHTS"` } type ComplexityLimit struct { diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index 9e83468220..264006fd6c 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -3531,6 +3531,11 @@ "expose_headers": { "type": "boolean", "description": "Enable costs response headers for estimated and actuals operation costs." + }, + "ignore_implementing_type_weights": { + "type": "boolean", + "default": false, + "description": "When enabled, ignores @cost weights from implementing types on abstract (interface/union) fields that have no weight of their own. Emulates Apollo's cost behavior." } } }, diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index fbc0f534f8..26a5835849 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -871,7 +871,8 @@ "Mode": "enforce", "MaxEstimatedLimit": 1000, "EstimatedListSize": 10, - "ExposeHeaders": false + "ExposeHeaders": false, + "IgnoreImplementingTypeWeights": false }, "DepthLimit": null, "ParserLimits": {