diff --git a/router-tests/go.mod b/router-tests/go.mod index 243b39b576..b81cc273f6 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.2 + github.com/wundergraph/graphql-go-tools/v2 v2.4.4 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/sdk v1.43.0 go.opentelemetry.io/otel/sdk/metric v1.43.0 diff --git a/router-tests/go.sum b/router-tests/go.sum index 233c05c80b..12a78bfb31 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.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2 h1:aNBJ9l8QQY7Yo4pqmny91GuLAPGAySDKW2ivUhaO9eo= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4 h1:VCvS9bku4ie7+St3+H5SNuVz6dtQiDKujqQ439yrMBM= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= 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/protocol/mcp_test.go b/router-tests/protocol/mcp_test.go index ded7b0fc17..8321d292e4 100644 --- a/router-tests/protocol/mcp_test.go +++ b/router-tests/protocol/mcp_test.go @@ -138,7 +138,7 @@ func TestMCP(t *testing.T) { Description: "This is a GraphQL query that retrieves a list of employees.", InputSchema: mcp.ToolInputSchema{ Type: "object", - Properties: map[string]interface{}{"criteria": map[string]interface{}{"additionalProperties": false, "description": "Allows to filter employees by their details.", "nullable": false, "properties": map[string]interface{}{"hasPets": map[string]interface{}{"nullable": true, "type": "boolean"}, "nationality": map[string]interface{}{"enum": []interface{}{"AMERICAN", "DUTCH", "ENGLISH", "GERMAN", "INDIAN", "SPANISH", "UKRAINIAN"}, "nullable": true, "type": "string"}, "nested": map[string]interface{}{"additionalProperties": false, "nullable": true, "properties": map[string]interface{}{"hasChildren": map[string]interface{}{"nullable": true, "type": "boolean"}, "maritalStatus": map[string]interface{}{"enum": []interface{}{"ENGAGED", "MARRIED"}, "nullable": true, "type": "string"}}, "type": "object"}}, "type": "object"}}, + Properties: map[string]interface{}{"criteria": map[string]interface{}{"additionalProperties": false, "description": "Allows to filter employees by their details.", "properties": map[string]interface{}{"hasPets": map[string]interface{}{"type": []interface{}{"boolean", "null"}}, "nationality": map[string]interface{}{"enum": []interface{}{"AMERICAN", "DUTCH", "ENGLISH", "GERMAN", "INDIAN", "SPANISH", "UKRAINIAN", nil}, "type": []interface{}{"string", "null"}}, "nested": map[string]interface{}{"additionalProperties": false, "properties": map[string]interface{}{"hasChildren": map[string]interface{}{"type": []interface{}{"boolean", "null"}}, "maritalStatus": map[string]interface{}{"enum": []interface{}{"ENGAGED", "MARRIED", nil}, "type": []interface{}{"string", "null"}}}, "type": []interface{}{"object", "null"}}}, "type": "object"}}, Required: []string(nil)}, RawInputSchema: json.RawMessage(nil), Annotations: mcp.ToolAnnotation{ @@ -298,7 +298,7 @@ Description: This is a GraphQL query that retrieves a list of employees. Input Schema: ` + bt + `json -{"additionalProperties":false,"description":"This is a GraphQL query that retrieves a list of employees.","nullable":true,"properties":{"criteria":{"additionalProperties":false,"description":"Allows to filter employees by their details.","nullable":false,"properties":{"hasPets":{"nullable":true,"type":"boolean"},"nationality":{"enum":["AMERICAN","DUTCH","ENGLISH","GERMAN","INDIAN","SPANISH","UKRAINIAN"],"nullable":true,"type":"string"},"nested":{"additionalProperties":false,"nullable":true,"properties":{"hasChildren":{"nullable":true,"type":"boolean"},"maritalStatus":{"enum":["ENGAGED","MARRIED"],"nullable":true,"type":"string"}},"type":"object"}},"type":"object"}},"type":"object"} +{"additionalProperties":false,"description":"This is a GraphQL query that retrieves a list of employees.","properties":{"criteria":{"additionalProperties":false,"description":"Allows to filter employees by their details.","properties":{"hasPets":{"type":["boolean","null"]},"nationality":{"enum":["AMERICAN","DUTCH","ENGLISH","GERMAN","INDIAN","SPANISH","UKRAINIAN",null],"type":["string","null"]},"nested":{"additionalProperties":false,"properties":{"hasChildren":{"type":["boolean","null"]},"maritalStatus":{"enum":["ENGAGED","MARRIED",null],"type":["string","null"]}},"type":["object","null"]}},"type":"object"}},"type":"object"} ` + bt + ` GraphQL Query: diff --git a/router-tests/security/costs_test.go b/router-tests/security/costs_test.go index befa2330f2..c9601527df 100644 --- a/router-tests/security/costs_test.go +++ b/router-tests/security/costs_test.go @@ -287,7 +287,7 @@ func TestOperationCost(t *testing.T) { require.Equal(t, "230", estimated) actual := res.Response.Header.Get(core.CostActualHeader) - require.Equal(t, "46", actual) // (7/3 + 13) * 3 + require.Equal(t, "24", actual) // 3 * (5.67 + 1*7/3) // Query 2: only employees-subgraph fields — Cosmo @cost(weight: 5) from employees applies res2 := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -300,7 +300,7 @@ func TestOperationCost(t *testing.T) { require.Equal(t, "150", estimated2) actual2 := res2.Response.Header.Get(core.CostActualHeader) - require.Equal(t, "22", actual2) + require.Equal(t, "14", actual2) }) }) diff --git a/router/core/graphql_handler.go b/router/core/graphql_handler.go index dd4166d859..f02817e8ac 100644 --- a/router/core/graphql_handler.go +++ b/router/core/graphql_handler.go @@ -227,13 +227,13 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { pw.reqCtx = reqCtx } if h.enableCostResponseHeaders && reqCtx.operation.costEstimatedSet { - // actualListSizes is populated by the resolver after resolution completes, + // ArrayStats is populated by the resolver after resolution completes, // and we need to set headers before actual write happens in the same resolver. - pw.costHeaderSetter = func(actualListSizes map[string]int) { + pw.costHeaderSetter = func(typeStats map[string]resolve.TypeNameStats) { pw.writer.Header().Set(CostEstimatedHeader, strconv.Itoa(reqCtx.operation.costEstimated)) - if actualListSizes != nil { + if typeStats != nil { if costCalc := reqCtx.operation.preparedPlan.preparedPlan.GetCostCalculator(); costCalc != nil { - actual := costCalc.ActualCost(resolveCtx.VariablesView(), actualListSizes) + actual := costCalc.ActualCost(resolveCtx.VariablesView(), typeStats) reqCtx.operation.costActual = actual reqCtx.operation.costActualSet = true pw.writer.Header().Set(CostActualHeader, strconv.Itoa(actual)) @@ -252,10 +252,10 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Compute actual cost for metrics/telemetry if not already set by the header callback - if !reqCtx.operation.costActualSet && resolveCtx.ActualListSizes != nil && + if !reqCtx.operation.costActualSet && resolveCtx.TypeNameStats != nil && reqCtx.operation.preparedPlan != nil && reqCtx.operation.preparedPlan.preparedPlan != nil { if costCalc := reqCtx.operation.preparedPlan.preparedPlan.GetCostCalculator(); costCalc != nil { - reqCtx.operation.costActual = costCalc.ActualCost(resolveCtx.VariablesView(), resolveCtx.ActualListSizes) + reqCtx.operation.costActual = costCalc.ActualCost(resolveCtx.VariablesView(), resolveCtx.TypeNameStats) reqCtx.operation.costActualSet = true } } diff --git a/router/core/header_rule_engine.go b/router/core/header_rule_engine.go index c25bf9d8a1..babc53d2f4 100644 --- a/router/core/header_rule_engine.go +++ b/router/core/header_rule_engine.go @@ -16,6 +16,10 @@ import ( "github.com/cespare/xxhash/v2" "github.com/expr-lang/expr/vm" cachedirective "github.com/pquerna/cachecontrol/cacheobject" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" "github.com/wundergraph/cosmo/router/internal/expr" "github.com/wundergraph/cosmo/router/internal/headers" @@ -25,9 +29,6 @@ import ( "github.com/wundergraph/graphql-go-tools/v2/pkg/ast" "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/datasource/graphql_datasource" "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "go.uber.org/zap" ) var ( @@ -103,7 +104,7 @@ type headerPropagationWriter struct { routerHeaderPropagation *HeaderPropagation reqCtx *requestContext didApplyRouterRespHeaders bool - costHeaderSetter func(actualListSizes map[string]int) + costHeaderSetter func(typeStats map[string]resolve.TypeNameStats) didSetCostHeaders bool } @@ -137,7 +138,7 @@ func (h *headerPropagationWriter) Write(p []byte) (n int, err error) { } if h.costHeaderSetter != nil && !h.didSetCostHeaders { h.didSetCostHeaders = true - h.costHeaderSetter(h.resolveCtx.ActualListSizes) + h.costHeaderSetter(h.resolveCtx.TypeNameStats) } return h.writer.Write(p) } diff --git a/router/go.mod b/router/go.mod index 4b911abda8..1d099273e3 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.2 + github.com/wundergraph/graphql-go-tools/v2 v2.4.4 // 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.43.0 diff --git a/router/go.sum b/router/go.sum index d05ae2b6ad..c0bb919835 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.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc= github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2 h1:aNBJ9l8QQY7Yo4pqmny91GuLAPGAySDKW2ivUhaO9eo= -github.com/wundergraph/graphql-go-tools/v2 v2.4.2/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4 h1:VCvS9bku4ie7+St3+H5SNuVz6dtQiDKujqQ439yrMBM= +github.com/wundergraph/graphql-go-tools/v2 v2.4.4/go.mod h1:7ljNHLrBOoOszCk4ir4Z+O6Yrf+vwBBmxjwqM3imVgA= 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=