diff --git a/apollo-router/src/configuration/expansion.rs b/apollo-router/src/configuration/expansion.rs index 3c079451a9..aa2d6f28d3 100644 --- a/apollo-router/src/configuration/expansion.rs +++ b/apollo-router/src/configuration/expansion.rs @@ -536,4 +536,30 @@ mod test { assert_yaml_snapshot!(value); }) } + + #[test] + fn test_dollar_escape() { + // Test that $$ is escaped to a literal $ by shellexpand + let expansion = Expansion::builder() + .mocked_env_var("API_HOST", "api.example.com") + .supported_mode("env") + .build(); + + let value = json!({ + // $$ should become a single $ + "literal_dollar": "some$$api$$key", + // $$ alongside ${env.VAR} expansion + "mixed": "https://${env.API_HOST}/path?price=$$100", + // Multiple $$ in a row + "multiple_escapes": "$$first $$second $$third", + // $$ at start and end + "edges": "$$start and end$$", + // No expansion needed (plain string) + "plain": "no dollars here" + }); + let result = expansion.expand(&value).expect("expansion must succeed"); + insta::with_settings!({sort_maps => true}, { + assert_yaml_snapshot!(result); + }) + } } diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__expansion__test__dollar_escape.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__expansion__test__dollar_escape.snap new file mode 100644 index 0000000000..7417e65867 --- /dev/null +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__expansion__test__dollar_escape.snap @@ -0,0 +1,9 @@ +--- +source: apollo-router/src/configuration/expansion.rs +expression: result +--- +edges: $start and end$ +literal_dollar: some$api$key +mixed: "https://api.example.com/path?price=$100" +multiple_escapes: $first $second $third +plain: no dollars here diff --git a/docs/source/routing/configuration/yaml.mdx b/docs/source/routing/configuration/yaml.mdx index 6bc50a79b9..c276190c7c 100644 --- a/docs/source/routing/configuration/yaml.mdx +++ b/docs/source/routing/configuration/yaml.mdx @@ -841,6 +841,13 @@ headers: Here, the `name` and `value` entries under `&insert_custom_header` are reused under `*insert_custom_header`. +### Escaping special characters + +To include a literal `$` character, double it as `$$`. The router converts each `$$` to a single `$`: + +- Config value: `prefix$$suffix` +- Result: `prefix$suffix` + ## Related topics - [Checklist for configuring the router for production](/technotes/TN0008-production-readiness-checklist/#apollo-router)