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.
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.