Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"ajv": "^8.12.0",
"asl-path-validator": "^0.16.1",
"commander": "^10.0.1",
"jsonata": "^2.0.6",
"jsonpath-plus": "^10.3.0",
"yaml": "^2.3.1"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata Expressions shouldn't have extra spaces",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% \n10 %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata Expressions should be surrounded like so: \"{% <JSONata expression> %}\"",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% 10 %",
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata Expressions should be surrounded like so: \"{% <JSONata expression> %}\"",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "10 %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata Expressions should be surrounded like so: \"{% <JSONata expression> %}\"",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{%10%}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
12 changes: 12 additions & 0 deletions src/__tests__/definitions/invalid-jsonata-syntax-arithmetic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata bad arithmetic error",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% 10 + * 2 %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONPath Syntax inside of a JSONata surround should not be valid",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% $.store.book[?(@.price < 10)].title %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "Missing the closing paren on `sum` function call",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% $sum($states.input.items.numbers %",
Comment thread
aidanprior marked this conversation as resolved.
Outdated
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata Expressions should be surrounded like so: \"{% <JSONata expression> %}\"",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% $sum($states.input.items.numbers %",
"QueryLanguage": "JSONata",
"End": true
}
}
Comment thread
aidanprior marked this conversation as resolved.
Outdated
}
12 changes: 12 additions & 0 deletions src/__tests__/definitions/valid-json-surround-syntax.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata Expressions should be surrounded like so: \"{% <JSONata expression> %}\"",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% 10 %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
12 changes: 12 additions & 0 deletions src/__tests__/definitions/valid-jsonata-syntax-function-call.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata function calls",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% $sum($states.input.numbers) %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata path expression",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% $states.input.data.items %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Comment": "JSONata simple arithmetic",
"StartAt": "X",
"States": {
"X": {
"Type": "Pass",
"Output": "{% 1+2 %}",
"QueryLanguage": "JSONata",
"End": true
}
}
}
78 changes: 78 additions & 0 deletions src/checks/jsonata-syntax-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import jsonata from "jsonata";
import {
StateMachine,
StateMachineError,
StateMachineErrorCode,
} from "../types";
import { getStates } from "./get-states";

type Value = string | number | boolean | Record<string, unknown>;

function checkField(
stateName: string,
fieldName: string,
value: Value
): StateMachineError[] {
if (typeof value === "object")
Comment thread
aidanprior marked this conversation as resolved.
Outdated
if (Array.isArray(value))
return value.flatMap((entry, index) =>
checkField(stateName, `${fieldName}[${index}]`, entry as Value)
);
else
Object.entries(value).flatMap(([innerFieldName, innerValue]) =>
checkField(
stateName,
fieldName + "/" + innerFieldName,
innerValue as Value
)
);
else if (typeof value === "string")
if (value.startsWith("{%") || value.endsWith("%}"))
if (/\{% .* %\}/.test(value)) {
try {
jsonata(value.match(/\{% (.*) %\}/)?.[1] ?? "");
return [];
} catch (error) {
return [
{
"Error code": StateMachineErrorCode.InvalidJsonataSyntax,
Message: `Invalid JSONata syntax in ${stateName}/${fieldName}: ${
(error as Error).message
}`,
},
];
}
} else
return [
{
"Error code": StateMachineErrorCode.InvalidJsonataSyntax,
Message: `Invalid JSONata syntax in ${stateName}/${fieldName}: JSONata surround syntax incomplete. Should be: "{% <JSONata expression> %}"`,
},
];
else return [];
else if (typeof value === "boolean") return [];
else if (typeof value === "number") return [];
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
else
throw Error(
`JSONata Syntax Validation Error: ${stateName}/${fieldName} is of type '${typeof value}' (${value}) which is not expected!`
Comment thread
aidanprior marked this conversation as resolved.
);
return [];
}

export function validateJsonataSyntax(
definition: StateMachine
): StateMachineError[] {
return getStates(definition.States)
.filter(
(entry) =>
(entry.state.QueryLanguage ??
definition.QueryLanguage ??
"JSONPath") === "JSONata"
)
.flatMap(({ state, stateName }) =>
Object.entries(state).flatMap(([fieldName, value]) =>
checkField(stateName, fieldName, value as Value)
)
);
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum StateMachineErrorCode {
FailCauseProperty = "FAIL_CAUSE_PROPERTY",
FailErrorProperty = "FAIL_ERROR_PROPERTY",
QueryLanguageFieldError = "QUERY_LANGUAGE_FIELD",
InvalidJsonataSyntax = "INVALID_JSONATA_SYNTAX",
}
export type StateMachineError = {
"Error code": StateMachineErrorCode;
Expand Down
2 changes: 2 additions & 0 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
stateChecks,
} from "./checks/state-checks";
import { StateEntry } from "./checks/get-states";
import { validateJsonataSyntax } from "./checks/jsonata-syntax-validator";

const DefaultOptions: ValidationOptions = {
checkPaths: true,
Expand Down Expand Up @@ -63,6 +64,7 @@ export = function validator(
errors.push(
...mustNotHaveDuplicateFieldNamesAfterEvaluation(definition, options)
);
errors.push(...validateJsonataSyntax(definition));
errors.push(
...stateChecks(definition, options, [
{
Expand Down