asl-validator
Version:
Amazon States Language validator
112 lines (101 loc) • 3.66 kB
text/typescript
import { JSONPath } from "jsonpath-plus";
import {
AslChecker,
StateMachine,
StateMachineError,
StateMachineErrorCode,
States,
} from "../types";
export const stateTransitionsErrors: AslChecker = (definition) => {
const errorMessages: StateMachineError[] = [];
// given a nested state machine, this function will examine
// each state and record its `Next` or `Default` values
// to see what states are reachable.
// Avoids traversing into Map or Parallel states since the
// states defined within those containers are not valid
// targets for states outside the containers.
const nextAndDefaultTargets = (nestedStateMachine: StateMachine) => {
const states: string[] = [];
Object.keys(nestedStateMachine.States).forEach((stateName) => {
const nestedState = nestedStateMachine.States[stateName];
const isContainer =
["Map", "Parallel"].indexOf(nestedState.Type as string) >= 0;
if (isContainer) {
states.push(
...JSONPath<string[]>({
json: nestedState,
path: "$.Next",
})
);
states.push(
...JSONPath<string[]>({
json: nestedState,
path: "$.Default",
})
);
} else {
states.push(
...JSONPath<string[]>({
json: nestedState,
path: "$..[Next,Default]",
})
);
}
});
return states;
};
// reports an error for each state that is found to be an invalid
// transition
const validateNestedStateMachine = (nestedStateMachine: StateMachine) => {
let availStateNames: string[] = [];
// don't traverse into any nested states. We only want to record the States
// that are immediately under the Branch.
// These are the only valid states to link to from within the branch
JSONPath<States[]>({
json: nestedStateMachine,
path: "$.States",
}).forEach((branchStates) => {
availStateNames = availStateNames.concat(Object.keys(branchStates));
});
// check that there are no transitions outside this branch
const targetedStates = nextAndDefaultTargets(nestedStateMachine);
return targetedStates.filter(
(state) => availStateNames.indexOf(state) === -1
);
};
// we know the step function is schema valid
// we know that every `Parallel` state has its expected `Branches` field
// we need to visit each Branch within a Parallel to ensure that it doesn't
// link outside its branch.
JSONPath<StateMachine[][]>({
json: definition,
path: "$..Branches",
}).forEach((parallelBranches) => {
parallelBranches.forEach((nestedStateMachine) => {
const errs = validateNestedStateMachine(nestedStateMachine).map(
(state) => ({
"Error code": StateMachineErrorCode.BranchOutboundTransitionTarget,
Message: `Parallel branch state cannot transition to target: ${state}`,
})
);
errorMessages.push(...errs);
});
});
// we know the step function is schema valid
// we know that every `Map` state has its expected `Iterator` field
// we need to visit the Iterator within a Map to ensure that it doesn't
// link outside its container.
JSONPath<StateMachine[]>({
json: definition,
path: "$..Iterator",
}).forEach((nestedStateMachine) => {
const errs = validateNestedStateMachine(nestedStateMachine).map(
(state) => ({
"Error code": StateMachineErrorCode.MapOutboundTransitionTarget,
Message: `Map branch state cannot transition to target: ${state}`,
})
);
errorMessages.push(...errs);
});
return errorMessages;
};