Skip to content

feat(stepfunctions): implement States.JsonMerge intrinsic#1662

Open
abanna wants to merge 5 commits into
floci-io:mainfrom
abanna:feat/stepfunctions-jsonmerge
Open

feat(stepfunctions): implement States.JsonMerge intrinsic#1662
abanna wants to merge 5 commits into
floci-io:mainfrom
abanna:feat/stepfunctions-jsonmerge

Conversation

@abanna

@abanna abanna commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the States.JsonMerge intrinsic function to the Step Functions executor. Previously any state using it failed with Unsupported intrinsic function: States.JsonMerge.

  • Implements the shallow merge form States.JsonMerge($.a, $.b, false): the result is a's top-level fields with b's top-level fields merged in, and b wins on a key conflict.
  • Rejects the deep-merge form (third argument true) and non-object arguments, matching AWS (which only supports false).
  • Also resolves true/false/null literal intrinsic arguments in resolveIntrinsicArg, so the third argument evaluates to a real boolean rather than falling through to path resolution.

Type of change

  • New feature (feat:)

AWS Compatibility

States.JsonMerge(json1, json2, isDeepMerge) performs a shallow merge of two JSON objects; the only supported isDeepMerge value is false, and conflicting top-level keys take the value from the second object. Deep merge (true) is rejected. Verified against the documented AWS intrinsic behavior and covered by AslExecutorJsonMergeTest.

Checklist

  • ./mvnw test passes locally (AslExecutorJsonMergeTest: 4 tests; sibling AslExecutorCatchTest regression green — run in an eclipse-temurin:25-jdk container)
  • New or updated integration test added (AslExecutorJsonMergeTest)
  • Commit messages follow Conventional Commits

The DPS provisioning state machine uses States.JsonMerge, which AslExecutor rejected with "Unsupported intrinsic function: States.JsonMerge". Implement the shallow merge (second object's top-level fields win on conflict); reject the deep-merge form (third argument true) and non-object arguments, as AWS does. Also resolve boolean/null literal intrinsic arguments so the third argument evaluates correctly. Covered by AslExecutorJsonMergeTest.
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR implements the States.JsonMerge intrinsic function in Floci's Step Functions executor, resolving a hard failure for state machines that rely on it. It also adds true/false/null literal recognition to resolveIntrinsicArg, which was needed to correctly evaluate the boolean third argument.

  • Shallow merge is performed by iterating a's then b's fields into a fresh ObjectNode, so b wins on key conflicts — matching AWS semantics.
  • The guard order is correct: argument count → boolean type check → object type check → deep-merge rejection, aligning with AWS error precedence.
  • Six targeted unit tests cover the happy path, all four rejection cases, and the error-ordering invariant.

Confidence Score: 5/5

Safe to merge — the change is self-contained, all validation paths are exercised, and the previously flagged issues with type coercion and error ordering have been corrected.

The implementation correctly handles all relevant validation cases for States.JsonMerge: argument count, boolean type guard (no silent coercion), object-type check before deep-merge rejection, and the shallow merge itself. The boolean/null literal additions to resolveIntrinsicArg are minimal and well-scoped. Six unit tests cover every branch including the error-ordering invariant.

No files require special attention.

Important Files Changed

Filename Overview
src/main/java/io/github/hectorvent/floci/services/stepfunctions/AslExecutor.java Adds the States.JsonMerge case to the intrinsic switch and boolean/null literal handling to resolveIntrinsicArg; validation order and type guards are correct.
src/test/java/io/github/hectorvent/floci/services/stepfunctions/AslExecutorJsonMergeTest.java New test class with 6 focused unit tests covering the happy path, all rejection paths, and the object-type-before-deep-merge error-ordering invariant.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["States.JsonMerge(arg1, arg2, arg3)"] --> B{"Exactly 3 args?"}
    B -- No --> E1["FailState: requires exactly 3 arguments"]
    B -- Yes --> C["resolveIntrinsicArg for all 3"]
    C --> D{"deepArg.isBoolean()"}
    D -- No --> E2["FailState: third argument must be a boolean"]
    D -- Yes --> F{"a.isObject() AND b.isObject()"}
    F -- No --> E3["FailState: requires two JSON objects"]
    F -- Yes --> G{"deep == true"}
    G -- Yes --> E4["FailState: supports only shallow merge"]
    G -- No --> H["Create new ObjectNode, set all fields from a, overwrite with all fields from b"]
    H --> I["Return merged ObjectNode"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["States.JsonMerge(arg1, arg2, arg3)"] --> B{"Exactly 3 args?"}
    B -- No --> E1["FailState: requires exactly 3 arguments"]
    B -- Yes --> C["resolveIntrinsicArg for all 3"]
    C --> D{"deepArg.isBoolean()"}
    D -- No --> E2["FailState: third argument must be a boolean"]
    D -- Yes --> F{"a.isObject() AND b.isObject()"}
    F -- No --> E3["FailState: requires two JSON objects"]
    F -- Yes --> G{"deep == true"}
    G -- Yes --> E4["FailState: supports only shallow merge"]
    G -- No --> H["Create new ObjectNode, set all fields from a, overwrite with all fields from b"]
    H --> I["Return merged ObjectNode"]
Loading

Reviews (5): Last reviewed commit: "Merge branch 'main' into feat/stepfuncti..." | Re-trigger Greptile

Comment thread src/main/java/io/github/hectorvent/floci/services/stepfunctions/AslExecutor.java Outdated
Comment thread src/main/java/io/github/hectorvent/floci/services/stepfunctions/AslExecutor.java Outdated
The third (deep) argument was read with JsonNode.asBoolean(), which silently
coerces null/number/string/object nodes to false, so States.JsonMerge($.a,$.b,$.x)
with a non-boolean $.x quietly performed a shallow merge instead of failing.
Validate that the resolved third argument is a boolean and raise States.Runtime
otherwise. Covered by AslExecutorJsonMergeTest.nonBooleanThirdArgumentRejected.
@hectorvent hectorvent added feature sfn AWS Step Functions labels Jul 1, 2026
abanna added 2 commits July 1, 2026 22:25
…-merge flag

AWS validates the argument types of States.JsonMerge before evaluating the deep-merge
flag, so two non-objects passed with true should report "requires two JSON objects"
rather than "shallow merge only". Reorder the object-type check ahead of the deep-flag
rejection and add a precedence test.
@abanna

abanna commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Applied the Greptile P2 on error precedence: the object-type check now runs before the deep-merge-flag rejection, so States.JsonMerge with two non-object arguments and true reports requires two JSON objects (type validation first) rather than shallow merge only, matching AWS's ordering. Added a precedence test. Branch is up to date with main (merge) and green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants