Skip to content

Conversation

@rrayst
Copy link
Member

@rrayst rrayst commented Feb 6, 2026

Summary by CodeRabbit

  • Documentation

    • Updated examples and tutorials to emit JSON values directly (unquoted expressions) and mark relevant payloads as application/json.
  • Templating

    • Enhanced template engine and JSON-aware escaping so inserted values are produced as proper JSON types; helper calls simplified.
  • Tests

    • Updated tests to validate JSON-typed outputs (arrays, values) instead of string-encoded representations.

@rrayst
Copy link
Member Author

rrayst commented Feb 6, 2026

/ok-to-test

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Templates across examples were changed to emit raw JSON values (unquoted expressions); core templating was updated with JSON-aware parameter bindings and escaping. A new StreamingTemplateEngine implementation and changes to GroovyBuiltInFunctions and related signatures enable JSON-aware template rendering.

Changes

Cohort / File(s) Summary
Core templating engine & bindings
core/src/main/java/com/predic8/membrane/core/interceptor/templating/TemplateInterceptor.java, core/src/main/java/com/predic8/membrane/core/lang/AbstractScriptInterceptor.java, core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java, core/src/main/java/com/predic8/membrane/core/lang/groovy/GroovyBuiltInFunctions.java, core/src/main/java/com/predic8/membrane/core/lang/groovy/GroovyExchangeExpression.java
Add JSON-aware flag to parameter binding calls; change createParameterBindings signature to accept isJSON; pass params into GroovyBuiltInFunctions; add escape(Object) hook and conditional JSON escaping.
New template engine implementation
core/src/main/java/com/predic8/membrane/core/lang/groovy/adapted/StreamingTemplateEngine.java
Add custom StreamingTemplateEngine with reimplemented StreamingTemplate: custom parsing, binding handling, error context/mangling, and template-to-Closure compilation.
Tests — template escaping & bindings
core/src/test/java/com/predic8/membrane/core/interceptor/templating/GroovyTemplateEscapingTest.java, core/src/test/java/com/predic8/membrane/core/interceptor/templating/TemplateInterceptorTest.java, core/src/test/java/com/predic8/membrane/core/lang/groovy/GroovyBuiltInFunctionsTest.java
Add/adjust tests for JSON vs non-JSON escaping, update constructor usage for GroovyBuiltInFunctions, and adapt assertions to JSON-typed outputs.
Examples & tutorials — JSON value emission
distribution/.../templating/json/*, distribution/.../orchestration/*, distribution/.../security/jwt/apikey-to-jwt-conversion/*, distribution/.../web-services-soap/rest2soap-template/*, distribution/.../extending-membrane/if/*, distribution/.../security/api-key/rbac/apis.yaml, distribution/.../transformation/*, distribution/tutorials/...
Replace quoted interpolations ("${...}" / fn.*) with unquoted expressions (${...}) or unscoped helpers (jsonPath(...), xpath(...), scopes()), emitting values as JSON tokens rather than JSON strings.
Example test asserting JSON array
distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/ApiKeyToJwtTest.java
Update assertion to expect scope as a JSON array instead of a quoted string.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant TemplateInterceptor
    participant ScriptingUtils
    participant GroovyBuiltInFunctions
    participant StreamingTemplateEngine
    participant Router

    Client->>TemplateInterceptor: incoming request
    TemplateInterceptor->>ScriptingUtils: createParameterBindings(..., producesJSON)
    ScriptingUtils->>GroovyBuiltInFunctions: construct binding (params map, escape hook)
    GroovyBuiltInFunctions-->>ScriptingUtils: binding map returned
    TemplateInterceptor->>StreamingTemplateEngine: render template with bindings
    StreamingTemplateEngine->>GroovyBuiltInFunctions: evaluate expressions (calls escape as needed)
    StreamingTemplateEngine-->>TemplateInterceptor: rendered output (JSON-aware)
    TemplateInterceptor-->>Client: send response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

7.x

Suggested reviewers

  • christiangoerdes

Poem

🐇 I hopped through templates in the night,
Dropped needless quotes to let values take flight,
Arrays parade and objects shine true,
Escape lends a hand when JSON is due,
Carrots and renders — a fresh launch for you! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.87% 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 'fix JSON escaping' accurately reflects the core changes across the PR: updates to template handling to emit unquoted JSON values and proper escaping behavior, including infrastructure changes to support JSON-aware template processing.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-scopes

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
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: 2

🤖 Fix all issues with AI agents
In `@distribution/examples/web-services-soap/rest2soap-template/proxies.xml`:
- Around line 23-24: The population property is being extracted as a string and
then serialized with fn.toJSON, producing a JSON string; update the extraction
to use numeric XPath so the JSON output is a number: change the setProperty that
defines "population" to use fn.xpath('number(//population)') (language="xpath")
so property.population is a numeric value and fn.toJSON(property.population)
yields a JSON number; locate the setProperty/usage around property.population
and replace the current string XPath extraction with
fn.xpath('number(//population)').

In `@distribution/tutorials/orchestration/30-Orchestration.yaml`:
- Around line 32-34: The JSON template emits a stray trailing quote because
fn.toJSON(property.title) and fn.toJSON(fn.jsonPath('$.name')) already return
quoted strings, but the template left an extra literal " after the author
expression; remove the extra double-quote following
fn.toJSON(fn.jsonPath('$.name')) so the "author" line reads with only the
fn.toJSON(...) expression (refer to fn.toJSON, fn.jsonPath('$.name'), and
property.title to locate the affected template).
🧹 Nitpick comments (2)
distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/ApiKeyToJwtTest.java (1)

32-32: Stale hardcoded TOKEN still encodes scope as a string.

The TOKEN constant on line 32 was generated with the old format where scope is "[accounting, finance]" (a string). The validJwt test on line 88 expects JWT Scopes: [accounting, finance], which works because the old token's string value renders that way.

However, this creates a discrepancy: validKey validates the new array format while validJwt implicitly relies on the old string format. If the TOKEN is ever regenerated to match the new scope format, the assertion on line 88 would likely need updating too (an array might render as ["accounting", "finance"] rather than [accounting, finance]).

Consider regenerating the TOKEN with the new scope format and updating line 88 accordingly, so both tests exercise the same contract.

Also applies to: 80-89

distribution/examples/orchestration/for-loop/apis.yaml (1)

33-34: Inconsistency: p.price not wrapped with fn.toJSON unlike the tutorial counterpart.

In distribution/tutorials/orchestration/50-Loop-get-Details.yaml (line 49), p.price is wrapped with fn.toJSON(p.price). Here it remains as raw ${p.price}. For consistency and safety (e.g., if the value is null or an unexpected type), consider wrapping it as well.

Suggested fix
-                          "name": ${fn.toJSON(p.name)},
-                          "price": ${p.price}
+                          "name": ${fn.toJSON(p.name)},
+                          "price": ${fn.toJSON(p.price)}

@rrayst rrayst marked this pull request as ready for review February 6, 2026 12:39
Copy link
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
`@core/src/main/java/com/predic8/membrane/core/interceptor/templating/TemplateInterceptor.java`:
- Around line 131-133: The producesJSON method in TemplateInterceptor currently
does a case-sensitive check (contentType.contains("json") ) which misses
uppercase MIME variants; update TemplateInterceptor.producesJSON to normalize
contentType (e.g., call contentType.toLowerCase(Locale.ROOT) or use
equalsIgnoreCase on the subtype) before searching for "json", and guard against
null by keeping the existing null check so the method returns true for any
case-insensitive occurrence of "json".

In
`@core/src/main/java/com/predic8/membrane/core/lang/groovy/adapted/StreamingTemplateEngine.java`:
- Around line 86-93: The template engine sets "delegate = _b.get('fn')" which
throws NPE when make(null) passes a null binding; in StreamingTemplateEngine
(the make()/make(null) path) guard the binding lookup by checking _b for null
(and that _b.get('fn') is non-null) before assigning delegate, or default to a
safe no-op delegate when _b is null so Template.make() and make(null) do not
NPE; update the code around the "delegate = _b.get('fn')" expression to use a
null-check on _b and only call get('fn') when _b != null.

In `@distribution/tutorials/transformation/60-SOAP-Array-to-JSON.yaml`:
- Around line 56-57: Remove the explicit double quotes around the name
expression in the JSON template so the <%= xpath('string(./f:name)', fruit) %>
output isn't double-quoted; replace the quoted pattern for the "name" field with
the unquoted expression (same style as the numeric "price" field) so
StreamingTemplateEngine's escape()/toJSON() returns a correctly-serialized JSON
string without extra quotes.
🧹 Nitpick comments (2)
core/src/test/java/com/predic8/membrane/core/interceptor/templating/TemplateInterceptorTest.java (1)

247-257: Test correctly validates the new unquoted JSON template syntax.

The switch from "${fn.user()}" to ${user()} relies on JSON-aware escaping to produce properly quoted string values. The assertion (contains("alice")) confirms the value is rendered. Consider also asserting that the output is valid JSON to ensure the escaping produces correct structure.

Optional: assert valid JSON output
         assertTrue(exc.getRequest().getBodyAsStringDecoded().contains("alice"));
+        // Verify the output is valid JSON
+        assertDoesNotThrow(() -> om.readTree(exc.getRequest().getBodyAsStringDecoded()));
core/src/test/java/com/predic8/membrane/core/interceptor/templating/GroovyTemplateEscapingTest.java (1)

39-47: Consider adding a test for values containing special JSON characters.

To fully validate the escaping fix (per the PR title "fix JSON escaping"), it would be valuable to test values that contain characters requiring JSON escaping (e.g., quotes, backslashes, newlines). This would confirm the fix handles the edge cases that likely motivated this PR.

@rrayst rrayst force-pushed the fix-scopes branch 2 times, most recently from 86ea70f to b404087 Compare February 9, 2026 20:47
Copy link
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

Caution

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

⚠️ Outside diff range comments (1)
distribution/examples/security/jwt/apikey-to-jwt-conversion/README.md (1)

22-24: ⚠️ Potential issue | 🟡 Minor

Prose still references the removed setProperty step.

Lines 22–24 mention "store the API key's scopes in a property named scopes" and the property being "used in a template." Since setProperty was removed and scopes() is now called directly in the template, this text is stale and should be updated to reflect the new approach.

🤖 Fix all issues with AI agents
In `@distribution/examples/security/jwt/apikey-to-jwt-conversion/README.md`:
- Line 36: The README snippet's <template> tag is missing the contentType
attribute; update the example so the <template> element includes
contentType="application/json" to match the actual proxies.xml (ensure the
README's <template> tag mirrors the one at line 16 in the real proxies.xml).
🧹 Nitpick comments (1)
core/src/main/java/com/predic8/membrane/core/lang/groovy/adapted/StreamingTemplateEngine.java (1)

479-482: AccessController.doPrivileged is deprecated for removal.

The @SuppressWarnings("removal") suppression and TODO at line 479 acknowledge this. Java 17+ deprecated AccessController and it's slated for removal. This is inherited from the original Groovy code, so no action needed now, but worth tracking.

@rrayst rrayst force-pushed the fix-scopes branch 4 times, most recently from 21efaab to ff5bc45 Compare February 10, 2026 15:13
@rrayst rrayst force-pushed the fix-scopes branch 2 times, most recently from 1a11aa8 to 9a55318 Compare February 11, 2026 12:42
@rrayst rrayst merged commit 13bece1 into master Feb 11, 2026
5 checks passed
@rrayst rrayst deleted the fix-scopes branch February 11, 2026 13:40
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