Skip to content

Add SpEL support for yaml configuration#2640

Merged
predic8 merged 19 commits into
masterfrom
config-spel-support
Jan 22, 2026
Merged

Add SpEL support for yaml configuration#2640
predic8 merged 19 commits into
masterfrom
config-spel-support

Conversation

@christiangoerdes
Copy link
Copy Markdown
Collaborator

@christiangoerdes christiangoerdes commented Jan 20, 2026

Summary by CodeRabbit

  • New Features

    • YAML scalars support sandboxed SpEL templates (#{...}) with env(...) lookups, including inside strings.
  • New Utilities

    • Added a restricted SpEL evaluation stack and a safe env accessor to securely resolve expressions.
  • Behavior

    • JSON Schema can emit multiple allowed types; some primitives may be represented as strings.
  • Refactor

    • YAML coercion and scalar conversion routed through a SpEL-aware, centralized conversion flow with improved error messages.
  • Tests & Tutorials

    • New tests and an environment-variables tutorial validating env(...) substitution and in-string templates.

✏️ Tip: You can customize this high-level summary in your review settings.

@christiangoerdes christiangoerdes marked this pull request as draft January 20, 2026 09:31
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Adds restricted SpEL evaluation for YAML scalars (env accessor, parser context, evaluation context and engine), integrates SpEL-aware scalar conversion into YAML coercion paths, and updates JSON schema generation to emit multi-type type arrays via typeAllowingString().

Changes

Cohort / File(s) Summary
Schema Type Handling
annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java, annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/AbstractSchema.java
Use .typeAllowingString(...) in schema construction; add typeList and typeAllowingString() to emit multi-type type arrays (e.g., ["integer","string"]).
SpEL Infrastructure
annot/src/main/java/com/predic8/membrane/annot/yaml/spel/HashTemplateParserContext.java, annot/src/main/java/com/predic8/membrane/annot/yaml/spel/SpELContext.java, annot/src/main/java/com/predic8/membrane/annot/yaml/spel/SpELContextFactory.java, annot/src/main/java/com/predic8/membrane/annot/yaml/spel/SpELEngine.java, annot/src/main/java/com/predic8/membrane/annot/yaml/spel/EnvPropertyAccessor.java
Add template parser context, an SpEL root type, an env() property accessor, a restricted StandardEvaluationContext factory, and a safe SpEL evaluation engine.
YAML Parsing Integration
annot/src/main/java/com/predic8/membrane/annot/yaml/YamlParsingUtils.java, annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java, annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java
Wire SPEL_CTX and resolveSpelValue; introduce convertScalarOrSpel; refactor coercion into textual vs non-textual flows with helpers for SpEL evaluation, numeric parsing, booleans, references, and consistent ParsingException messaging.
Tests & Tutorials
annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java, distribution/src/test/java/com/predic8/membrane/tutorials/advanced/EnvironmentVariablesTutorialTest.java, distribution/tutorials/advanced/90-Environment-Variables.yaml
Add unit tests for env()-based YAML substitution, a tutorial YAML demonstrating env() usage, and a tutorial test that starts the proxy with PORT env set.
Whitespace-only tutorial edits
distribution/tutorials/advanced/20-Path-Parameter-Routing.yaml, distribution/tutorials/advanced/30-Path-Rewriting.yaml, distribution/tutorials/advanced/50-Redirects.yaml, distribution/tutorials/advanced/80-Removing-HTTP-Headers.yaml
Removed leading blank lines; no functional changes.
Minor test formatting
distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/ConfigurationPropertiesTest.java
Reflowed RestAssured chain indentation; no behavioral change.

Sequence Diagram(s)

sequenceDiagram
    participant YAML as "YAML Source"
    participant Generic as "GenericYamlParser"
    participant SpELEngine as "SpELEngine"
    participant SPEL_CTX as "SPEL_CTX"
    participant Method as "MethodSetter"
    participant Schema as "JsonSchemaGenerator"

    YAML->>Generic: supply scalar node (e.g. "#{env.PORT}" or "pre-#{env.PORT}-suf")
    Generic->>Generic: convertScalarOrSpel(node, targetType)
    alt node contains SpEL template
        Generic->>SpELEngine: eval(template, SPEL_CTX)
        SpELEngine->>SPEL_CTX: request env('PORT')
        SPEL_CTX-->>SpELEngine: resolvedValue
        SpELEngine-->>Generic: resolvedValue
    else no template
        Generic->>Generic: SCALAR_MAPPER.convertValue(node, targetType)
    end
    Generic-->>Method: pass resolved value for coercion
    Method->>Method: coerceTextual / coerceNonTextual (numbers, booleans, refs, maps)
    Method-->>Generic: coercedValue
    Generic->>Schema: createProperty(...).typeAllowingString(type)
    Schema-->>Schema: emit `type` or `type` array (e.g., ["integer","string"])
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

7.x

Suggested reviewers

  • predic8
  • rrayst

Poem

🐇 I nudge an env() and watch it sing,

templates unfurl like a bright little spring.
Scalars found voice, types learned to share,
parsers and schemas hop light as air.
Hooray—values hop home with room to spare!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add SpEL support for yaml configuration' clearly and concisely describes the main purpose of the pull request, which is to add Spring Expression Language (SpEL) support to YAML configuration processing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/AbstractSchema.java`:
- Around line 110-116: The method typeAllowingString currently sets typeList
when STRING_ALLOWED_TYPES contains the input and sets type otherwise but does
not clear the alternate field, which can leave stale values and make json() emit
incorrect types; update typeAllowingString in AbstractSchema so that when
assigning this.typeList = List.of(type, "string") you also clear this.type (set
to null/empty), and when assigning this.type = type you clear this.typeList (set
to null/empty), ensuring only one of type or typeList is populated before json()
is called.

@membrane-ci-server
Copy link
Copy Markdown

This pull request needs "/ok-to-test" from an authorized committer.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@annot/src/main/java/com/predic8/membrane/annot/yaml/YamlParsingUtils.java`:
- Around line 123-139: resolveSpelValue currently returns null when a SpEL
expression yields null, which later causes unclear errors if the targetType is a
primitive; change the null-handling so that after evaluating value (in
resolveSpelValue) you check if value == null and targetType.isPrimitive(), and
if so throw a new ParsingException with a clear message like "SpEL expression
evaluated to null for primitive target type: <type>" including the existing
node; otherwise keep returning null for non-primitive targets. Use the existing
ParsingException and node variable so callers get a helpful error.
- Around line 53-59: The current newSpelContext() uses StandardEvaluationContext
which still allows reflective method/property access even with a blocked type
locator; change it to use SimpleEvaluationContext (or, if keeping
StandardEvaluationContext, install a locked-down MethodResolver and
PropertyAccessor) so only the intended env(...) functions on SpelRoot are
available; update newSpelContext() to create a SimpleEvaluationContext rooted
with new SpelRoot and explicitly register only the allowed functions/properties,
or add a custom MethodResolver/PropertyAccessor that rejects all calls except
the env handler to strictly limit evaluation surface.

In `@annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java`:
- Around line 597-623: Test spelEnvInYamlScalar is flaky because it relies on a
fixed environment variable MEMBRANE_SPEL_TEST; change the test to generate a
unique env key (e.g. MEMBRANE_SPEL_TEST_<randomSuffix>), programmatically set
that env var to a numeric value before calling parseYAML and use the same
generated key inside the YAML string, then clean up the env var after the
assertion; update spelEnvInYamlScalar to build the YAML with the generated key
and ensure parseYAML is invoked with that deterministic numeric value.
🧹 Nitpick comments (1)
annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java (1)

122-131: Replace assert with explicit runtime validation.
Line 130 uses assert, which is disabled in production; if node is ever null, this will later surface as a less actionable NPE. Consider throwing a ParsingException (or Objects.requireNonNull) with a clear message instead.

Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/YamlParsingUtils.java Outdated
Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/YamlParsingUtils.java Outdated
Comment thread annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java (1)

123-147: SpEL short‑circuit breaks non‑String setters and references.
Returning early for any textual node bypasses enum/reference/primitive coercion and feeds a String into non‑String setters, which will fail for references or quoted numeric/boolean values.

🐛 Proposed fix: only evaluate SpEL then keep existing coercion
@@
-import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.TextNode;
@@
-        if (node != null && node.isTextual()) {
-            return resolveSpelValue(node.asText(), String.class, node);
-        }
+        if (node != null && node.isTextual()) {
+            String raw = node.asText();
+            if (raw.contains("#{")) {
+                Object resolved = resolveSpelValue(raw, String.class, node);
+                if (resolved == null) return null;
+                node = TextNode.valueOf(String.valueOf(resolved));
+            }
+        }
♻️ Duplicate comments (2)
annot/src/main/java/com/predic8/membrane/annot/yaml/YamlParsingUtils.java (1)

118-131: Fail fast when SpEL resolves to null for primitives.
Returning null here still yields a confusing reflective setter error for primitive targets.

🐛 Proposed guard for null → primitive
-        if (value == null) return null;
+        if (value == null) {
+            if (targetType.isPrimitive()) {
+                throw new ParsingException(
+                        "SpEL expression evaluated to null for primitive %s; provide a default value."
+                                .formatted(targetType.getSimpleName()),
+                        node);
+            }
+            return null;
+        }
annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java (1)

597-651: Avoid env‑dependent test flakiness.
These tests depend on a fixed env key; if CI sets it, defaults won’t be used.

🔧 Suggested adjustment (randomized env key)
@@
-import java.util.List;
+import java.util.List;
+import java.util.UUID;
@@
-        BeanRegistry br = parseYAML(result, """
+        String envKey = "MEMBRANE_SPEL_TEST_" + UUID.randomUUID().toString().replace("-", "");
+        BeanRegistry br = parseYAML(result, """
                 root:
-                  timeout: "#{env('MEMBRANE_SPEL_TEST','42')}"
-                """);
+                  timeout: "#{env('%s','42')}"
+                """.formatted(envKey));
@@
-        BeanRegistry br = parseYAML(result, """
+        String envKey = "MEMBRANE_SPEL_TEST_" + UUID.randomUUID().toString().replace("-", "");
+        BeanRegistry br = parseYAML(result, """
         root:
-          msg: "prefix-#{env('MEMBRANE_SPEL_TEST','X')}-suffix"
-        """);
+          msg: "prefix-#{env('%s','X')}-suffix"
+        """.formatted(envKey));

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java`:
- Around line 204-206: The conditional block in MethodSetter that checks "if
(node.isTextual() && isBeanReference(wanted)) { return resolveReference(ctx,
node, key, wanted); }" is unreachable because MethodSetter only calls this
method when !node.isTextual(); remove this dead code to avoid confusion and
duplicated logic; ensure no other callers depend on this branch and keep textual
bean-reference handling in coerceTextual (lines around coerceTextual and
resolveReference) unchanged.

Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java`:
- Around line 167-168: The bean-reference path in MethodSetter is re-reading the
raw node text so SpEL-evaluated values are ignored; update the logic in
MethodSetter (around coerceTextual and the branch that calls
isBeanReference/resolveReference) to pass the evaluated string (the result
produced by coerceTextual, e.g. variable v) into resolveReference instead of
calling node.asText() again; do the same fix for the other occurrence around
lines where resolveReference is called (the 227-233 block) so both
bean-reference branches use the evaluated value rather than the raw "#{...}"
text.

Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java Outdated
…and add specific handling for String targets.
predic8
predic8 previously approved these changes Jan 20, 2026
Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java Outdated
Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java Outdated
Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java Outdated
Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java Outdated
Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java Outdated
Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java Outdated
@christiangoerdes christiangoerdes marked this pull request as ready for review January 20, 2026 13:57
@christiangoerdes
Copy link
Copy Markdown
Collaborator Author

/ok-to-test

predic8
predic8 previously approved these changes Jan 21, 2026
Comment thread distribution/tutorials/advanced/90-Environment-Variables.yaml
Comment thread annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java Outdated
Comment thread distribution/tutorials/advanced/90-Environment-Variables.yaml Outdated
@predic8 predic8 merged commit b83c153 into master Jan 22, 2026
5 checks passed
@predic8 predic8 deleted the config-spel-support branch January 22, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants