Skip to content

swaggerexpert/arazzo-criterion

@swaggerexpert/arazzo-criterion

npmversion npm Test workflow Dependabot enabled try on RunKit Tidelift

Arazzo Criterion Objects specify the conditions used in successCriteria of a Step Object and in the criteria of Success and Failure Action Objects.

@swaggerexpert/arazzo-criterion is a parser, validator and evaluator for the simple type of Arazzo Criterion conditions only. The regex, jsonpath and xpath criterion types are out of scope — they delegate to external engines (a regular-expression engine, a JSONPath engine, an XPath engine) and belong in a higher-level evaluator that composes this package with those.

The simple condition syntax combines literals, comparison and logical operators, property de-reference / index accessors, and Arazzo Runtime Expressions. Runtime Expression operands are parsed by delegating to @swaggerexpert/arazzo-runtime-expression, so their sub-ASTs match that package exactly.

In an Arazzo document, a simple criterion appears in a step's successCriteria (or an action's criteria). The type defaults to simple, so a criterion is usually just a condition string:

steps:
  - stepId: getPet
    # ...
    successCriteria:
      - condition: $statusCode == 200
      - condition: $response.body.status == 'available' && $response.body.pets[0].id > 0

Written out as full Criterion Objects, those two entries are:

successCriteria:
  - condition: $statusCode == 200
    type: simple                      # the default; may be omitted
  - condition: $response.body.status == 'available' && $response.body.pets[0].id > 0
    type: simple

This library parses, validates and evaluates the condition string of such simple criteria.

It supports the simple Criterion Object condition defined in the following Arazzo specification versions:

Tidelift Get professionally supported @swaggerexpert/arazzo-criterion with Tidelift Subscription.

Table of Contents

Getting started

Installation

 $ npm install @swaggerexpert/arazzo-criterion

Usage

Parsing

Parsing a criterion condition is as simple as importing the parse function and calling it.

import { parse } from '@swaggerexpert/arazzo-criterion';

const parseResult = parse('$statusCode == 200 && $response.body.data != null');

parseResult variable has the following shape:

{
  result: <ParseResult['result']>,
  tree: <ParseResult['tree']>,
  stats: <ParseResult['stats']>,
  trace: <ParseResult['trace']>,
}

TypeScript typings are available for all fields attached to the parse result object returned by the parse function.

Translators

@swaggerexpert/arazzo-criterion provides several translators to convert the parse result into different tree representations.

CST translator

Concrete Syntax Tree (Parse tree) representation is available on the parse result when an instance of CSTTranslator is provided via the translator option to the parse function. CST is suitable to be consumed by other tools like IDEs, editors, etc...

import { parse, CSTTranslator } from '@swaggerexpert/arazzo-criterion';

const { tree: cst } = parse('$statusCode == 200', { translator: new CSTTranslator() });

CST tree has a shape documented by TypeScript typings (CSTNode).

AST translator

Default translator. Abstract Syntax Tree representation is available on the parse result by default or when an instance of ASTTranslator is provided via the translator option to the parse function. AST is suitable to be consumed by implementations that need to analyze or evaluate the condition.

import { parse } from '@swaggerexpert/arazzo-criterion';

const { tree: ast } = parse('$statusCode == 200 && $response.body.data != null');

or

import { parse, ASTTranslator } from '@swaggerexpert/arazzo-criterion';

const { tree: ast } = parse('$statusCode == 200', { translator: new ASTTranslator() });

AST tree has a shape documented by TypeScript typings (ConditionAST).

The AST produced for $statusCode == 200 && $response.body.data != null is:

{
  type: 'LogicalExpression',
  operator: '&&',
  left: {
    type: 'BinaryExpression',
    operator: '==',
    left: { type: 'RuntimeExpression', text: '$statusCode', expression: { type: 'StatusCodeExpression' } },
    right: { type: 'Literal', valueType: 'number', value: 200 },
  },
  right: {
    type: 'BinaryExpression',
    operator: '!=',
    left: {
      type: 'RuntimeExpressionNavigation',
      expression: { type: 'RuntimeExpression', text: '$response.body', expression: {  ...  } },
      navigation: [{ type: 'MemberAccess', name: 'data' }],
    },
    right: { type: 'Literal', valueType: 'null', value: null },
  },
}

A runtime expression with no accessors is represented as a RuntimeExpression node directly; a RuntimeExpressionNavigation node appears only when there is at least one .member / [index] accessor.

XML translator

An XML string representation of the parse tree is available on the parse result when an instance of XMLTranslator is provided via the translator option to the parse function.

import { parse, XMLTranslator } from '@swaggerexpert/arazzo-criterion';

const { tree: xml } = parse('$statusCode == 200', { translator: new XMLTranslator() });
Statistics

parse returns additional statistical information about the parsing process. Collection of the statistics can be enabled by setting the stats option to true.

import { parse } from '@swaggerexpert/arazzo-criterion';

const { stats } = parse('$statusCode == 200', { stats: true });

stats.displayStats(); // returns operator statistics as string
Tracing

parse returns additional tracing information about the parsing process. Tracing can be enabled by setting the trace option to true. Tracing is essential for debugging failed parses or analyzing rule execution flow.

import { parse } from '@swaggerexpert/arazzo-criterion';

const { result, trace } = parse('$statusCode <', { trace: true });

result.success; // false
trace.displayTrace(); // returns trace information as string

Tracing also allows you to infer expected tokens at a failure point. This is useful for generating meaningful syntax error messages.

import { parse } from '@swaggerexpert/arazzo-criterion';

const { trace } = parse('$statusCode nonsense', { trace: true });

const expectations = trace.inferExpectations();
console.log(expectations.toString()); // the tokens that could appear at the failure point

Validation

import { test } from '@swaggerexpert/arazzo-criterion';

test('$statusCode == 200'); // => true
test('$statusCode < 200 < 300'); // => false (chained comparisons are invalid)

Evaluation

evaluate runs a condition against caller-supplied values. Because a criterion only becomes concrete once its runtime expressions are resolved against a live context, you provide a resolve function. It receives the runtime expression string and its parsed sub-AST, and returns the concrete value — so you can key on either the raw string or dispatch on the AST type.

import { evaluate } from '@swaggerexpert/arazzo-criterion';

const context = {
  $statusCode: 200,
  '$response.body': { status: 'Available', data: [{ id: 42 }] },
};
const resolve = (expression, ast) => context[expression];

evaluate('$statusCode == 200', { resolve }); // => true
evaluate("$response.body.status == 'available'", { resolve }); // => true (case-insensitive)
evaluate('$response.body.data[0].id > 10', { resolve }); // => true

Evaluation follows the "loose comparison" rules from the Arazzo specification:

  • string comparisons are case-insensitive;
  • numeric strings are coerced to numbers when compared with a number;
  • null is equal only to null; any relational comparison involving null is false;
  • a condition passes when it evaluates to a truthy value and fails on false, null, or a missing value.

Errors

@swaggerexpert/arazzo-criterion provides a structured error class hierarchy, enabling precise error handling across parsing and evaluation.

import {
  ArazzoCriterionError,
  ArazzoCriterionParseError,
  ArazzoCriterionEvaluateError,
} from '@swaggerexpert/arazzo-criterion';

ArazzoCriterionError is the base class for all errors. ArazzoCriterionParseError wraps an unexpected error raised during parsing, and ArazzoCriterionEvaluateError is thrown by evaluate (for example, when the condition is not valid). Both extend ArazzoCriterionError and include the offending condition.

import { evaluate, ArazzoCriterionEvaluateError } from '@swaggerexpert/arazzo-criterion';

try {
  evaluate('== 200', { resolve: () => undefined }); // invalid condition
} catch (error) {
  if (error instanceof ArazzoCriterionEvaluateError) {
    console.log(error.condition); // the condition that failed: "== 200"
  }
}

Note: a syntactically invalid condition passed to parse does not throw - it returns a result with result.success === false and tree === undefined. Use test for a simple boolean validity check.

Grammar

New grammar instance can be created in the following way:

import { Grammar } from '@swaggerexpert/arazzo-criterion';

const grammar = new Grammar();

To obtain the original ABNF grammar as a string:

import { Grammar } from '@swaggerexpert/arazzo-criterion';

const grammar = new Grammar();

grammar.toString();
// or
String(grammar);

More about the simple criterion condition

The logical / comparison spine follows the structure of RFC 9535 (JSONPath filter expressions): a logical layer (||, &&, !, grouping) that composes only booleans, over a flat, non-recursive comparison layer (comparable OP comparable). This rejects nonsensical forms such as chained comparisons (a < b < c) at the grammar level.

Operands are Arazzo Runtime Expressions, optionally followed by property-dereference (.member) and index ([n]) accessors. The grammar matches an operand as a single bounded token; the runtime-expression base and the trailing accessors are separated during AST construction by delegating the base to @swaggerexpert/arazzo-runtime-expression (see the grammar header note for why the boundary cannot be expressed in a context-free grammar).

The simple criterion condition is defined by the following ABNF (RFC 5234) syntax:

; Arazzo Criterion Object - "simple" condition ABNF syntax
; https://spec.openapis.org/arazzo/v1.1.0.html#criterion-object

; ---------------------------------------------------------------------------
; Criterion condition (simple type)
; ---------------------------------------------------------------------------

condition        = S logical-expr S

logical-expr     = logical-or-expr
logical-or-expr  = logical-and-expr *( S "||" S logical-and-expr )
logical-and-expr = basic-expr *( S "&&" S basic-expr )

basic-expr       = paren-expr / comparison-expr / test-expr
paren-expr       = [ logical-not-op S ] "(" S logical-expr S ")"
test-expr        = [ logical-not-op S ] comparable
comparison-expr  = comparable S comparison-op S comparable

logical-not-op   = "!"
comparison-op    = "==" / "!=" / "<=" / ">=" / "<" / ">"

; ---------------------------------------------------------------------------
; Comparables: literals or operands (runtime expression + navigation, matched
; as one bounded token)
; ---------------------------------------------------------------------------

comparable                 = literal / runtime-expression-operand
runtime-expression-operand = "$" 1*operand-char
operand-char               = %x22-25 / %x27 / %x2A-3B / %x3F-5A / %x5B-5D / %x5E-7A / %x7E / %x80-10FFFF

; Navigation over the resolved runtime-expression value (secondary entry point;
; parsed from the operand remainder during AST construction).
runtime-expression-navigation = 1*( member-access / index-access )
member-access                 = "." member-name
index-access                  = "[" index "]"
member-name                   = 1*( ALPHA / DIGIT / "_" / "-" )
index                         = 1*DIGIT

; ---------------------------------------------------------------------------
; Literals
; ---------------------------------------------------------------------------

literal          = number / string / boolean / null
boolean          = "true" / "false"
null             = "null"

number           = ( int / "-0" ) [ frac ] [ exp ]
int              = "0" / ( [ "-" ] DIGIT1 *DIGIT )
frac             = "." 1*DIGIT
exp              = ( "e" / "E" ) [ "-" / "+" ] 1*DIGIT
DIGIT1           = %x31-39   ; 1-9 non-zero digit

; single-quoted; a literal quote is escaped by doubling it ('')
string           = squote *( escaped-quote / string-char ) squote
escaped-quote    = squote squote                 ; '' represents a single '
string-char      = %x20-26 / %x28-10FFFF         ; any char except squote (%x27)
squote           = %x27                           ; '

; ---------------------------------------------------------------------------
; Whitespace (optional blank space), per RFC 9535
; ---------------------------------------------------------------------------

S                = *B
B                = %x20 / %x09 / %x0A / %x0D      ; space, tab, LF, CR

; ---------------------------------------------------------------------------
; Core rules (RFC 5234 B.1)
; ---------------------------------------------------------------------------

ALPHA          = %x41-5A / %x61-7A   ; A-Z / a-z
DIGIT          = %x30-39             ; 0-9

License

@swaggerexpert/arazzo-criterion is licensed under Apache 2.0 license. @swaggerexpert/arazzo-criterion comes with an explicit NOTICE file containing additional legal notices and information.