Resolve placeholders at map level before YAML/JSON serialization#3235
Conversation
026eebf to
fdcede7
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes invalid YAML/JSON emitted by the Config Server when resolving placeholders that expand to multiline values, by making placeholder resolution output-format aware.
Changes:
- Introduces
EnvironmentPropertySource.OutputFormatand a format-awareresolvePlaceholders(...)overload. - Updates
EnvironmentControllerYAML/JSON endpoints to resolve placeholders using the appropriate format behavior (YAML block scalars, JSON newline escaping). - Adds unit/integration tests covering multiline placeholder expansion in YAML and JSON responses.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/EnvironmentPropertySource.java | Adds OutputFormat and new YAML/JSON-specific placeholder resolution logic (block scalars / JSON escaping). |
| spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java | Routes YAML/JSON output through the new format-aware placeholder resolution. |
| spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/support/EnvironmentPropertySourceTest.java | Adds focused unit tests for YAML block scalar and JSON escaping behavior. |
| spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java | Adds controller-level tests verifying resolved multiline values round-trip correctly in YAML/JSON. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
28af6c1 to
655a49c
Compare
|
I think it would be better to try and resolve these placeholders before we convert it to a string. For example in |
5ced426 to
e1e6556
Compare
|
@ryanjbaxter I figured it would be better to do the resolution when the data is still structured, but I wasn't sure. Thanks for the steer! |
ryanjbaxter
left a comment
There was a problem hiding this comment.
Could we add a test for default values (${key:default}) and ignoreUnresolvablePlaceholders=true with a key ${MISSING} in EnvironmentPropertySourceTest?
I think these changes should target the 4.3.x branch and I can merge it forward into main as well.
Instead of resolving placeholders by string-manipulating serialized
YAML/JSON output, resolve them in the property Map before passing to
the serializer. This lets SnakeYAML and Jackson handle multiline values
and escaping natively, eliminating the need for:
- OutputFormat enum
- resolveYamlPlaceholders / resolveAsBlockScalar / isStandalonePlaceholder
- resolvePlaceholdersWithTransform with JsonStringEncoder
- joinMultiLinePlaceholders
Add resolveMapPlaceholders() which recursively walks Maps and Lists,
resolving ${...} in String values via PropertyPlaceholderHelper with
key trimming (handles SnakeYAML-folded multi-line placeholders).
The text-based resolvePlaceholders() remains for .properties output.
Re: spring-cloud#3234
Signed-off-by: Gabriel Schulhof <gschulhof@auction.com>
e1e6556 to
b7ac585
Compare
Signed-off-by: Gabriel Schulhof <gschulhof@auction.com>
b7ac585 to
df93bd7
Compare
|
@ryanjbaxter I have addressed your review comments. Please take another look! |
| public class EnvironmentPropertySource extends PropertySource<Environment> { | ||
|
|
||
| // Use PropertyPlaceholderHelper directly to trim whitespace from keys | ||
| private static final PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}", ":", null, true); |
There was a problem hiding this comment.
Please use snake yaml casing
There was a problem hiding this comment.
@ryanjbaxter I'm sorry! I don't quite understand. Where should I use such casing?
There was a problem hiding this comment.
Sorry I meant upper snake case, ie. PROPERTY_PLACEHOLDER_HELPER
There was a problem hiding this comment.
Thanks for clarifying! Fixed.
|
|
||
| // Verify YAML is valid and values are resolved | ||
| Map<String, Object> reparsed = new Yaml().load(yaml); | ||
| java.util.List<Object> items = (java.util.List<Object>) reparsed.get("items"); |
There was a problem hiding this comment.
Sorry about that! Forgot to commit. These should all be using imports now.
| @Test | ||
| public void defaultValueUsedWhenKeyMissing() { | ||
| Environment environment = new Environment("test", "default"); | ||
| java.util.Map<String, Object> map = new java.util.LinkedHashMap<>(); |
| environment.add(new PropertySource("one", map)); | ||
| StandardEnvironment prepared = prepareEnvironment(environment); | ||
|
|
||
| Map<String, Object> input = new java.util.LinkedHashMap<>(); |
| @Test | ||
| public void defaultValueOverriddenWhenKeyExists() { | ||
| Environment environment = new Environment("test", "default"); | ||
| java.util.Map<String, Object> map = new java.util.LinkedHashMap<>(); |
| environment.add(new PropertySource("one", map)); | ||
| StandardEnvironment prepared = prepareEnvironment(environment); | ||
|
|
||
| Map<String, Object> input = new java.util.LinkedHashMap<>(); |
| @Test | ||
| public void unresolvablePlaceholderLeftIntact() { | ||
| Environment environment = new Environment("test", "default"); | ||
| java.util.Map<String, Object> map = new java.util.LinkedHashMap<>(); |
| environment.add(new PropertySource("one", map)); | ||
| StandardEnvironment prepared = prepareEnvironment(environment); | ||
|
|
||
| Map<String, Object> input = new java.util.LinkedHashMap<>(); |
Signed-off-by: Gabriel Schulhof <gschulhof@auction.com>
Signed-off-by: Gabriel Schulhof <gschulhof@auction.com>
Instead of resolving placeholders by string-manipulating serialized YAML/JSON output, resolve them in the property Map before passing to the serializer. This lets SnakeYAML and Jackson handle multiline values and escaping natively, eliminating the need for:
Add resolveMapPlaceholders() which recursively walks Maps and Lists, resolving ${...} in String values via PropertyPlaceholderHelper with key trimming (handles SnakeYAML-folded multi-line placeholders).
The text-based resolvePlaceholders() remains for .properties output.
Re: #3234