Skip to content

Validation Error in ZenEngine v0.51.5: "missing field statements" When Creating Decision with switchNode in JSON Rule #422

@sameradel84-adel

Description

@sameradel84-adel

Environment:

ZenEngine version: 0.51.5
Node.js version: [Add your Node.js version here, e.g., v20.11.0]
OS: [Add your OS here, e.g., Windows 11]

Problem Description:
I'm using ZenEngine to create a custom rule engine for a pricing microservice in a NestJS application. When trying to load a JSON rule that includes switchNode types for conditional branching (e.g., based on city, zone, and street), the engine throws a validation error: "missing field statements" (or sometimes "missing field expressions" depending on the format I try).
This happens during engine.createDecision(ruleJson), and the service falls back to a default mode. I've tried multiple formats for the switchNode content (based on the docs at docs.gorules.io, which describe conditions in Zen Expression Language but lack full JSON examples), including:

Using cases with simple keys (e.g., 'riyadh').
Using expressions with condition strings (e.g., "city == 'riyadh'") and "true" for default.
Adding statements: [] inside or outside content.
Adding hitPolicy: 'first'.

None of these work consistently, and the error persists. The docs mention "dynamic branching" but don't provide a complete JSON schema or example for switchNode in raw JSON (only general descriptions). This makes it hard to implement without trial and error.
Reproduction Steps:

Install @gorules/zen-engine@0.51.5.
Create a new ZenEngine instance: const engine = new ZenEngine();.
Try to create a decision with the following JSON rule (example below).
Call engine.createDecision(ruleJson).
Observe the error: "missing field statements".

Example Rule JSON (that fails):
JSON{
"id": "riyadh-pricing-v2",
"name": "Riyadh Dynamic Pricing v2",
"startNode": "start",
"nodes": [
{
"id": "start",
"name": "Start",
"type": "inputNode",
"next": "city-check",
"content": {}
},
{
"id": "city-check",
"name": "City Check",
"type": "switchNode",
"content": {
"hitPolicy": "first",
"expressions": {
"city == 'riyadh'": { "next": "zone-config" },
"true": { "next": "fallback" }
}
}
},
{
"id": "zone-config",
"name": "Zone Configuration",
"type": "switchNode",
"content": {
"hitPolicy": "first",
"expressions": {
"zone == 'north'": { "next": "street-config", "config": { "baseFare": 10, "perKm": 2.8, "perMinute": 0.6, "minFare": 12 } },
"zone == 'south'": { "next": "street-config", "config": { "baseFare": 9, "perKm": 2.5, "perMinute": 0.55, "minFare": 11 } },
"zone == 'east'": { "next": "street-config", "config": { "baseFare": 9, "perKm": 2.6, "perMinute": 0.55, "minFare": 11 } },
"zone == 'west'": { "next": "street-config", "config": { "baseFare": 10, "perKm": 2.7, "perMinute": 0.6, "minFare": 12 } },
"zone == 'central'": { "next": "street-config", "config": { "baseFare": 12, "perKm": 3.2, "perMinute": 0.75, "minFare": 15 } },
"true": { "next": "street-config", "config": { "baseFare": 10, "perKm": 2.7, "perMinute": 0.6, "minFare": 12 } }
}
}
},
{
"id": "street-config",
"name": "Street Configuration",
"type": "switchNode",
"content": {
"hitPolicy": "first",
"expressions": {
"street == 'king_fahd_rd'": { "next": "surge", "config": { "baseFare": 14, "perKm": 3.8, "perMinute": 0.9, "minFare": 18 } },
"street == 'olaya_st'": { "next": "surge", "config": { "baseFare": 13, "perKm": 3.5, "perMinute": 0.85, "minFare": 17 } },
"street == 'tahlia_st'": { "next": "surge", "config": { "baseFare": 15, "perKm": 4, "perMinute": 1, "minFare": 20 } },
"street == 'airport_rd'": { "next": "surge", "config": { "baseFare": 16, "perKm": 4.2, "perMinute": 1.1, "minFare": 22 } },
"true": { "next": "surge", "config": { "baseFare": 10, "perKm": 2.7, "perMinute": 0.6, "minFare": 12 } }
}
}
},
{
"id": "surge",
"name": "Surge Calculation",
"type": "expressionNode",
"content": {
"statements": [
{
"assign": {
"surge": "demandLevel == 'high' || [7,8,9,17,18,19,20].includes(currentHour) ? 1.4 : 1.0"
}
}
],
"next": "calc"
}
},
{
"id": "calc",
"name": "Final Price Calculation",
"type": "expressionNode",
"content": {
"statements": [
{ "assign": { "base": "config.baseFare" } },
{ "assign": { "distanceFare": "distanceKm * config.perKm" } },
{ "assign": { "timeFare": "durationMin * config.perMinute" } },
{ "assign": { "subtotal": "base + distanceFare + timeFare" } },
{ "assign": { "total": "subtotal * surge" } },
{ "assign": { "finalPrice": "total > config.minFare ? total : config.minFare" } }
],
"output": {
"finalPrice": "finalPrice",
"baseFare": "base",
"distanceFare": "distanceFare",
"timeFare": "timeFare",
"surgeMultiplier": "surge",
"currency": "SAR"
}
}
},
{
"id": "fallback",
"name": "Fallback Pricing",
"type": "expressionNode",
"content": {
"statements": [{ "assign": { "finalPrice": 25 } }],
"output": {
"finalPrice": "finalPrice",
"currency": "SAR",
"configUsed": "fallback"
}
}
}
]
}
Expected Behavior:
The decision should load without validation errors, allowing conditional branching based on input variables like city, zone, and street.
Actual Behavior:
Throws error: "missing field statements" (or similar, like "missing field expressions" in other formats).
Additional Info:

The rule is stored as JSON in a database and loaded via repo.getActiveRule().
I'm using NestJS for the service.
No custom validation on my side beyond basic checks (e.g., startNode existence).

Could you provide:

The exact JSON schema for switchNode in v0.51.5?
A complete example JSON for a simple branching graph using switchNode?

Thanks for your help! This would be hugely appreciated as the docs lack JSON examples.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions