Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,56 @@ def _clean_schema(self, validator: Validator, instance: Any):
if ql == "JSONata":
yield self._convert_schema_to_jsonata(), ql_validator

def _validate_start_at(
self,
definition: Any,
k: str,
add_path_to_message: bool,
path: deque | None = None
) -> ValidationResult:
"""
Per the Amazon States Language specification, 'StartAt must' reference
a valid state name that exists in the States object.

Reference: https://states-language.net/spec.html#toplevelfields
"""

start_at = definition.get("StartAt")
states = definition.get("States")

# Check if StartAt is missing
if start_at is None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if start_at is None:
if not isinstance(start_at, str) and not isinstance(states, dict):

return # Early return to avoid further checks

# Check if StartAt state exists in States object
if start_at not in states:
if path is None: # Top level StartAt
error_path = deque([k, "StartAt"])
display_path = "/StartAt"
else: # Nested StartAt like Parallel or Map
error_path = deque([k] + list(path) + ["StartAt"])
display_path = f"/{'/'.join(str(item) for item in path)}/StartAt"

message = f"MISSING_TRANSITION_TARGET: Missing 'Next' target '{start_at}' at {display_path}"

yield ValidationError(message, path=error_path,rule=self)

# Validate nested StartAt in Parallel and Map states
for state_name, state in states.items():

state_type = state.get("Type")

if state_type == "Parallel":
branches = state.get("Branches", [])
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
branches = state.get("Branches", [])
branches = state.get("Branches", [])
if not isinstance(branches, list):
continue

for idx, branch in enumerate(branches):
branch_path = deque(["States", state_name, "Branches", idx])
yield from self._validate_start_at(branch, k, add_path_to_message, branch_path)

if state_type == "Map":
processor = state.get("ItemProcessor")
processor_path = deque(["States", state_name, "ItemProcessor"])
yield from self._validate_start_at(processor, k, add_path_to_message, processor_path)

def _validate_step(
self,
validator: Validator,
Expand Down Expand Up @@ -134,6 +184,9 @@ def _validate_step(

yield self._clean_error(err)

# Validate StartAt exists
yield from self._validate_start_at(value, k, add_path_to_message)

def validate(
self, validator: Validator, keywords: Any, instance: Any, schema: dict[str, Any]
) -> ValidationResult:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,92 @@ def rule():
path=deque(
["Definition", "States", "Notify Failure", "Parameters"]
),
)
],
),
(
"Missing StartAt target",
{
"Definition": {
"StartAt": "FAIL",
"States": {
"Pass": {
"Type": "Pass",
"Next": "Success",
},
"Success": {
"Type": "Succeed",
},
},
}
},
[
ValidationError(
"MISSING_TRANSITION_TARGET: Missing 'Next' target 'FAIL' at /StartAt",
rule=StateMachineDefinition(),
path=deque(["Definition", "StartAt"]),
),
],
),
(
"Parallel state with missing StartAt target",
{
"Definition": {
"StartAt": "ParallelState",
"States": {
"ParallelState": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "FAIL",
"States": {
"BranchState": {
"Type": "Pass",
"End": True,
},
},
}
],
"End": True,
},
},
}
},
[
ValidationError(
"MISSING_TRANSITION_TARGET: Missing 'Next' target 'FAIL' at /States/ParallelState/Branches/0/StartAt",
rule=StateMachineDefinition(),
path=deque(["Definition", "States", "ParallelState", "Branches", 0, "StartAt"]),
),
],
),
(
"Map state with missing StartAt target in ItemProcessor",
{
"Definition": {
"StartAt": "MapState",
"States": {
"MapState": {
"Type": "Map",
"ItemProcessor": {
"StartAt": "FAIL",
"States": {
"ProcessItem": {
"Type": "Pass",
"End": True,
},
},
},
"End": True,
},
},
}
},
[
ValidationError(
"MISSING_TRANSITION_TARGET: Missing 'Next' target 'FAIL' at /States/MapState/ItemProcessor/StartAt",
rule=StateMachineDefinition(),
path=deque(["Definition", "States", "MapState", "ItemProcessor", "StartAt"]),
),
],
),
Expand Down