Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
115 changes: 115 additions & 0 deletions router-tests/error_handling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,121 @@ func TestFallbackErrors(t *testing.T) {
})
}

func TestAllowedExtensions(t *testing.T) {
t.Parallel()

t.Run("in wrapped mode, only allowed extensions should be included in the propagated error", func(t *testing.T) {
testenv.Run(t, &testenv.Config{
ModifySubgraphErrorPropagation: func(cfg *config.SubgraphErrorPropagationConfiguration) {
cfg.Enabled = true
cfg.Mode = config.SubgraphErrorPropagationModeWrapped
cfg.AllowedExtensionFields = []string{"allowed"}
},
Subgraphs: testenv.SubgraphsConfig{
Employees: testenv.SubgraphConfig{
Middleware: func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_, wErr := w.Write([]byte(`{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed","notAllowed":"notAllowed"}}]}`))
require.NoError(t, wErr)
})
},
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `{ employees { id } }`,
})
require.Equal(t, `{"errors":[{"message":"Failed to fetch from Subgraph 'employees'.","extensions":{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed"}}],"statusCode":403}}],"data":{"employees":null}}`, res.Body)
})
})

t.Run("in wrapped mode, with AllowAllExtensionFields set, all extensions should be included in the propagated error", func(t *testing.T) {
testenv.Run(t, &testenv.Config{
ModifySubgraphErrorPropagation: func(cfg *config.SubgraphErrorPropagationConfiguration) {
cfg.Enabled = true
cfg.Mode = config.SubgraphErrorPropagationModeWrapped
cfg.AllowedExtensionFields = []string{"allowed"}
cfg.AllowAllExtensionFields = true
},
Subgraphs: testenv.SubgraphsConfig{
Employees: testenv.SubgraphConfig{
Middleware: func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_, wErr := w.Write([]byte(`{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed","notAllowed":"notAllowed"}}]}`))
require.NoError(t, wErr)
})
},
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `{ employees { id } }`,
})
require.Equal(t, `{"errors":[{"message":"Failed to fetch from Subgraph 'employees'.","extensions":{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed","notAllowed":"notAllowed"}}],"statusCode":403}}],"data":{"employees":null}}`, res.Body)
})
})

t.Run("in passthrough mode, only allowed extensions should be included in the propagated error", func(t *testing.T) {
testenv.Run(t, &testenv.Config{
ModifySubgraphErrorPropagation: func(cfg *config.SubgraphErrorPropagationConfiguration) {
cfg.Enabled = true
cfg.Mode = config.SubgraphErrorPropagationModePassthrough
cfg.AllowedExtensionFields = []string{"allowed"}
},
Subgraphs: testenv.SubgraphsConfig{
Employees: testenv.SubgraphConfig{
Middleware: func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_, wErr := w.Write([]byte(`{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed","notAllowed":"notAllowed"}}]}`))
require.NoError(t, wErr)
})
},
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `{ employees { id } }`,
})
require.Equal(t, `{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed","statusCode":403}}],"data":{"employees":null}}`, res.Body)
})
})

t.Run("in passthrough mode, with AllowAllExtensionFields set, all extensions should be included in the propagated error", func(t *testing.T) {
testenv.Run(t, &testenv.Config{
ModifySubgraphErrorPropagation: func(cfg *config.SubgraphErrorPropagationConfiguration) {
cfg.Enabled = true
cfg.Mode = config.SubgraphErrorPropagationModePassthrough
cfg.AllowedExtensionFields = []string{"allowed"}
cfg.AllowAllExtensionFields = true
},
Subgraphs: testenv.SubgraphsConfig{
Employees: testenv.SubgraphConfig{
Middleware: func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_, wErr := w.Write([]byte(`{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed","notAllowed":"notAllowed"}}]}`))
require.NoError(t, wErr)
})
},
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `{ employees { id } }`,
})
require.Equal(t, `{"errors":[{"message":"Unauthorized","extensions":{"allowed":"allowed","notAllowed":"notAllowed","statusCode":403}}],"data":{"employees":null}}`, res.Body)
})
})

}

func TestErrorPropagation(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 1 addition & 1 deletion router-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/wundergraph/cosmo/demo v0.0.0-20250707145555-35d60cac85d9
github.com/wundergraph/cosmo/demo/pkg/subgraphs/projects v0.0.0-20250616075713-f2b99c96cec4
github.com/wundergraph/cosmo/router v0.0.0-20250707145555-35d60cac85d9
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.202
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.203
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/sdk v1.28.0
go.opentelemetry.io/otel/sdk/metric v1.28.0
Expand Down
4 changes: 2 additions & 2 deletions router-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,8 @@ github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTB
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
github.com/wundergraph/consul/sdk v0.0.0-20250204115147-ed842a8fd301 h1:EzfKHQoTjFDDcgaECCCR2aTePqMu9QBmPbyhqIYOhV0=
github.com/wundergraph/consul/sdk v0.0.0-20250204115147-ed842a8fd301/go.mod h1:wxI0Nak5dI5RvJuzGyiEK4nZj0O9X+Aw6U0tC1wPKq0=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.202 h1:C0rQNddwMMou4o1iXHhXiOlY6/IUF2yS9Pw3u3hd+ts=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.202/go.mod h1:DaBrBCMgKGd3t7zg7z11jKm+0mVJiesr/IQCRG9qgP0=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.203 h1:qTMYS9EICDCoMY90ILE3eW2/i1VNMhmyl79qpw5v6xc=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.203/go.mod h1:DaBrBCMgKGd3t7zg7z11jKm+0mVJiesr/IQCRG9qgP0=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
Expand Down
3 changes: 2 additions & 1 deletion router/core/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func (b *ExecutorConfigurationBuilder) Build(ctx context.Context, opts *Executor
AttachServiceNameToErrorExtensions: opts.RouterEngineConfig.SubgraphErrorPropagation.AttachServiceName,
DefaultErrorExtensionCode: opts.RouterEngineConfig.SubgraphErrorPropagation.DefaultExtensionCode,
AllowedSubgraphErrorFields: opts.RouterEngineConfig.SubgraphErrorPropagation.AllowedFields,
AllowAllErrorExtensionFields: opts.RouterEngineConfig.SubgraphErrorPropagation.AllowAllExtensionFields,
MaxRecyclableParserSize: opts.RouterEngineConfig.Execution.ResolverMaxRecyclableParserSize,
MultipartSubHeartbeatInterval: opts.HeartbeatInterval,
MaxSubscriptionFetchTimeout: opts.RouterEngineConfig.Execution.SubscriptionFetchTimeout,
Expand All @@ -100,7 +101,7 @@ func (b *ExecutorConfigurationBuilder) Build(ctx context.Context, opts *Executor
}

if opts.ApolloRouterCompatibilityFlags.SubrequestHTTPError.Enabled {
options.ResolvableOptions.ApolloRouterCompatibilitySubrequestHTTPError = true
options.ApolloRouterCompatibilitySubrequestHTTPError = true
}

switch opts.RouterEngineConfig.SubgraphErrorPropagation.Mode {
Expand Down
2 changes: 1 addition & 1 deletion router/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.0.0-rc.202
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.203
// Do not upgrade, it renames attributes we rely on
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
go.opentelemetry.io/contrib/propagators/b3 v1.23.0
Expand Down
4 changes: 2 additions & 2 deletions router/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,8 @@ github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0
github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk=
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.202 h1:C0rQNddwMMou4o1iXHhXiOlY6/IUF2yS9Pw3u3hd+ts=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.202/go.mod h1:DaBrBCMgKGd3t7zg7z11jKm+0mVJiesr/IQCRG9qgP0=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.203 h1:qTMYS9EICDCoMY90ILE3eW2/i1VNMhmyl79qpw5v6xc=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.203/go.mod h1:DaBrBCMgKGd3t7zg7z11jKm+0mVJiesr/IQCRG9qgP0=
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=
Expand Down
23 changes: 12 additions & 11 deletions router/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -724,16 +724,17 @@ const (
)

type SubgraphErrorPropagationConfiguration struct {
Enabled bool `yaml:"enabled" envDefault:"true" env:"SUBGRAPH_ERROR_PROPAGATION_ENABLED"`
PropagateStatusCodes bool `yaml:"propagate_status_codes" envDefault:"false" env:"SUBGRAPH_ERROR_PROPAGATION_STATUS_CODES"`
Mode SubgraphErrorPropagationMode `yaml:"mode" envDefault:"wrapped" env:"SUBGRAPH_ERROR_PROPAGATION_MODE"`
RewritePaths bool `yaml:"rewrite_paths" envDefault:"true" env:"SUBGRAPH_ERROR_PROPAGATION_REWRITE_PATHS"`
OmitLocations bool `yaml:"omit_locations" envDefault:"true" env:"SUBGRAPH_ERROR_PROPAGATION_OMIT_LOCATIONS"`
OmitExtensions bool `yaml:"omit_extensions" envDefault:"false" env:"SUBGRAPH_ERROR_PROPAGATION_OMIT_EXTENSIONS"`
AttachServiceName bool `yaml:"attach_service_name" envDefault:"true" env:"SUBGRAPH_ERROR_PROPAGATION_ATTACH_SERVICE_NAME"`
DefaultExtensionCode string `yaml:"default_extension_code" envDefault:"DOWNSTREAM_SERVICE_ERROR" env:"SUBGRAPH_ERROR_PROPAGATION_DEFAULT_EXTENSION_CODE"`
AllowedExtensionFields []string `yaml:"allowed_extension_fields" envDefault:"code" env:"SUBGRAPH_ERROR_PROPAGATION_ALLOWED_EXTENSION_FIELDS"`
AllowedFields []string `yaml:"allowed_fields" env:"SUBGRAPH_ERROR_PROPAGATION_ALLOWED_FIELDS"`
Enabled bool `yaml:"enabled" envDefault:"true" env:"ENABLED"`
PropagateStatusCodes bool `yaml:"propagate_status_codes" envDefault:"false" env:"STATUS_CODES"`
Mode SubgraphErrorPropagationMode `yaml:"mode" envDefault:"wrapped" env:"MODE"`
RewritePaths bool `yaml:"rewrite_paths" envDefault:"true" env:"REWRITE_PATHS"`
OmitLocations bool `yaml:"omit_locations" envDefault:"true" env:"OMIT_LOCATIONS"`
OmitExtensions bool `yaml:"omit_extensions" envDefault:"false" env:"OMIT_EXTENSIONS"`
AttachServiceName bool `yaml:"attach_service_name" envDefault:"true" env:"ATTACH_SERVICE_NAME"`
DefaultExtensionCode string `yaml:"default_extension_code" envDefault:"DOWNSTREAM_SERVICE_ERROR" env:"DEFAULT_EXTENSION_CODE"`
AllowAllExtensionFields bool `yaml:"allow_all_extension_fields" envDefault:"false" env:"ALLOW_ALL_EXTENSION_FIELDS"`
AllowedExtensionFields []string `yaml:"allowed_extension_fields" envDefault:"code" env:"ALLOWED_EXTENSION_FIELDS"`
AllowedFields []string `yaml:"allowed_fields" env:"ALLOWED_FIELDS"`
}

type StorageProviders struct {
Expand Down Expand Up @@ -1009,7 +1010,7 @@ type Config struct {

WebSocket WebSocketConfiguration `yaml:"websocket,omitempty"`

SubgraphErrorPropagation SubgraphErrorPropagationConfiguration `yaml:"subgraph_error_propagation"`
SubgraphErrorPropagation SubgraphErrorPropagationConfiguration `yaml:"subgraph_error_propagation" envPrefix:"SUBGRAPH_ERROR_PROPAGATION_"`

StorageProviders StorageProviders `yaml:"storage_providers"`
ExecutionConfig ExecutionConfig `yaml:"execution_config"`
Expand Down
5 changes: 5 additions & 0 deletions router/pkg/config/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2706,6 +2706,11 @@
"default": ["code"],
"description": "The allowed extension fields. The allowed extension fields are used to specify which fields of the Subgraph errors are allowed to be propagated to the client."
},
"allow_all_extension_fields": {
"type": "boolean",
"default": false,
"description": "Allow all extension fields from Subgraph errors to be propagated to the client. If the value is true (default: false), all extension fields from Subgraph errors will be propagated, overriding the allowed_extension_fields configuration."
},
Comment thread
endigma marked this conversation as resolved.
"omit_locations": {
"type": "boolean",
"default": true,
Expand Down
15 changes: 14 additions & 1 deletion router/pkg/config/fixtures/full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ events:
redis:
- id: my-redis
urls:
- "redis://localhost:6379/11"
- 'redis://localhost:6379/11'
cluster_enabled: true

engine:
Expand Down Expand Up @@ -445,6 +445,19 @@ automatic_persisted_queries:
provider_id: redis
object_prefix: 'cosmo_apq'

subgraph_error_propagation:
mode: pass-through
rewrite_paths: true
attach_service_name: true
default_extension_code: DOWNSTREAM_SERVICE_ERROR
omit_locations: true
omit_extensions: true
propagate_status_codes: false
allowed_extension_fields:
- 'field1'
- 'field2'
allow_all_extension_fields: true

execution_config:
storage:
provider_id: s3
Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/testdata/config_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@
"OmitExtensions": false,
"AttachServiceName": true,
"DefaultExtensionCode": "DOWNSTREAM_SERVICE_ERROR",
"AllowAllExtensionFields": false,
"AllowedExtensionFields": [
"code"
],
Expand Down
8 changes: 5 additions & 3 deletions router/pkg/config/testdata/config_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -747,14 +747,16 @@
"SubgraphErrorPropagation": {
"Enabled": true,
"PropagateStatusCodes": false,
"Mode": "wrapped",
"Mode": "pass-through",
"RewritePaths": true,
"OmitLocations": true,
"OmitExtensions": false,
"OmitExtensions": true,
"AttachServiceName": true,
"DefaultExtensionCode": "DOWNSTREAM_SERVICE_ERROR",
"AllowAllExtensionFields": true,
"AllowedExtensionFields": [
"code"
"field1",
"field2"
],
"AllowedFields": null
},
Expand Down
Loading