Skip to content

feat(stepfunctions): resolve payload-template .$ inside arrays and $$ context in intrinsic args#1691

Open
abanna wants to merge 1 commit into
floci-io:mainfrom
abanna:feat/stepfunctions-payload-array-and-context
Open

feat(stepfunctions): resolve payload-template .$ inside arrays and $$ context in intrinsic args#1691
abanna wants to merge 1 commit into
floci-io:mainfrom
abanna:feat/stepfunctions-payload-array-and-context

Conversation

@abanna

@abanna abanna commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Two AWS States Language parity fixes in the Step Functions payload-template / intrinsic path.

1. resolveParameters now recurses into arrays

A Parameters / ItemSelector / ResultSelector payload template must resolve .$ references at any nesting depth. Previously resolveParameters recursed into nested objects but passed array elements through verbatim, so a .$ reference inside an array was never substituted.

Real-world shape this fixes — an ECS Overrides.ContainerOverrides[].Environment[].Value.$:

"Parameters": {
  "Overrides": {
    "ContainerOverrides": [
      { "Name": "app", "Environment": [ { "Name": "ID", "Value.$": "$.systemId" } ] }
    ]
  }
}

Without array recursion, Value.$ stayed literal and the launched task got an empty environment.

2. Intrinsic arguments can reference the Context Object ($$)

resolvePath / evaluateIntrinsic / resolveIntrinsicArg now thread the Context Object through, so an intrinsic argument can read $$:

States.Format('id={}', $$.Map.Item.Value.systemId)
$$.Execution.Id

Ordinary $./States.* input-only resolution is unchanged — context is null on that path, so existing behavior is preserved.

Tests

  • AslExecutorArrayParamsTest.$ resolution inside arrays.
  • AslExecutorIntrinsicContextTest$$ references in intrinsic args (and that input-only resolution still ignores context).

Built + tested in the temurin container (targeted SFN suite green).

… context in intrinsic args

Two AWS States Language parity fixes in the payload-template / intrinsic path:

- resolveParameters now recurses into arrays, not just objects. A Parameters / ItemSelector /
  ResultSelector template resolves .$ references at any nesting depth, including inside arrays
  (e.g. an ECS Overrides.ContainerOverrides[].Environment[].Value.$). Previously array elements
  passed through verbatim, so those references were never substituted.
- Intrinsic-function arguments can now reference the Context Object ($$). resolvePath /
  evaluateIntrinsic / resolveIntrinsicArg thread the Context Object through, so an argument like
  States.Format('{}', $$.Map.Item.Value.x) or a bare $$.Execution.Id resolves against the
  context. Ordinary $. / input-only resolution is unchanged (context is null there).

Adds AslExecutorArrayParamsTest and AslExecutorIntrinsicContextTest.
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds two AWS States Language parity fixes to AslExecutor: resolveParameters now recurses into arrays (fixing .$ references inside nested array structures like ECS ContainerOverrides), and the Context Object ($$) is threaded through evaluateIntrinsic/resolveIntrinsicArg so intrinsic arguments like States.Format(..., $$.Map.Item.Value.x) resolve correctly.

  • Array recursion: the else if (val.isObject()) guard is widened to || val.isArray(), and a new if (parameters.isArray()) block handles arrays at any depth, correctly passing through primitives unchanged.
  • Context threading: a new 3-arg resolvePath overload is added; all intrinsic helpers are updated to accept and forward context; the 2-arg overload delegates with null context, preserving existing input-only resolution behavior exactly.
  • Tests: two new focused unit test classes cover both features, including a regression guard confirming $$ args yield null when no context is supplied.

Confidence Score: 4/5

Safe to merge; both fixes are narrow and well-guarded, with no changes to existing call sites outside the step-functions package.

The core logic changes are correct and backward-compatible. The only issue found is that the two new test files don't follow the *ServiceTest.java naming convention from AGENTS.md — a style concern with no impact on runtime behavior.

The two new test files (AslExecutorArrayParamsTest and AslExecutorIntrinsicContextTest) should be reviewed for naming convention alignment.

Important Files Changed

Filename Overview
src/main/java/io/github/hectorvent/floci/services/stepfunctions/AslExecutor.java Two well-scoped fixes: array recursion in resolveParameters and Context Object threading through evaluateIntrinsic/resolveIntrinsicArg. Backward compatibility preserved via null-context guard and a 2-arg resolvePath delegate. Logic is correct.
src/test/java/io/github/hectorvent/floci/services/stepfunctions/AslExecutorArrayParamsTest.java Covers the ECS ContainerOverrides/$. array resolution scenario well; file naming doesn't follow the *ServiceTest.java convention required by AGENTS.md.
src/test/java/io/github/hectorvent/floci/services/stepfunctions/AslExecutorIntrinsicContextTest.java Good coverage of States.Format with mixed $/$$. args, whole-context serialisation, and the no-context regression guard; same naming-convention concern as the companion test file.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[resolveParameters\nparameters, input, context] --> B{isObject?}
    B -- yes --> C{key ends with .$?}
    C -- yes, path starts $$.  --> D[resolvePath $.+path, context]
    C -- yes, path == $$ --> E[return context]
    C -- yes, else --> F[resolvePath path, input, context ★]
    C -- no, isObject OR isArray ★ --> G[recurse resolveParameters val]
    C -- no, primitive --> H[return val as-is]
    B -- no --> I{isArray? ★}
    I -- yes --> J[for each element\nrecurse resolveParameters element]
    I -- no --> K[return parameters]

    F --> L[resolvePath\npath, root, context]
    L --> M{starts with States.*?}
    M -- yes --> N[evaluateIntrinsic\nexpr, root, context ★]
    N --> O[resolveIntrinsicArg\narg, root, context ★]
    O --> P{context != null\nand arg starts $$. ?}
    P -- yes --> Q[resolvePath $. + suffix, context]
    P -- no, $$ exact --> R[return context]
    P -- no, $.path --> S[resolvePath arg, root, context]
    P -- no, literal / number --> T[return literal node]
    P -- no, fallthrough --> U[resolvePath arg, root, context\nnested States.* intrinsic]
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[resolveParameters\nparameters, input, context] --> B{isObject?}
    B -- yes --> C{key ends with .$?}
    C -- yes, path starts $$.  --> D[resolvePath $.+path, context]
    C -- yes, path == $$ --> E[return context]
    C -- yes, else --> F[resolvePath path, input, context ★]
    C -- no, isObject OR isArray ★ --> G[recurse resolveParameters val]
    C -- no, primitive --> H[return val as-is]
    B -- no --> I{isArray? ★}
    I -- yes --> J[for each element\nrecurse resolveParameters element]
    I -- no --> K[return parameters]

    F --> L[resolvePath\npath, root, context]
    L --> M{starts with States.*?}
    M -- yes --> N[evaluateIntrinsic\nexpr, root, context ★]
    N --> O[resolveIntrinsicArg\narg, root, context ★]
    O --> P{context != null\nand arg starts $$. ?}
    P -- yes --> Q[resolvePath $. + suffix, context]
    P -- no, $$ exact --> R[return context]
    P -- no, $.path --> S[resolvePath arg, root, context]
    P -- no, literal / number --> T[return literal node]
    P -- no, fallthrough --> U[resolvePath arg, root, context\nnested States.* intrinsic]
Loading

Reviews (1): Last reviewed commit: "feat(stepfunctions): resolve payload-tem..." | Re-trigger Greptile

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Test naming convention mismatch

AGENTS.md specifies that unit tests should follow the *ServiceTest.java naming pattern. Both new test files (AslExecutorArrayParamsTest and AslExecutorIntrinsicContextTest) use a different suffix. While AslExecutor is not technically a service, establishing an inconsistent test-naming pattern here may cause confusion when locating tests with build tooling or running focused test commands like ./mvnw test -Dtest=AslExecutor*.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

1 participant