gorules / zen

Open-source Business Rules Engine for your Rust, NodeJS, Python or Go applications.
https://gorules.io
MIT License
782 stars 73 forks source link

Error when creating a decision: invalid type: null, expected a string #258

Closed raymondKelly closed 3 hours ago

raymondKelly commented 4 hours ago

Firstly, thank you for building such a great tool. I hope to contribute in the future as my time on open-source projects permits.

Summary:

When creating a ZenDecision using ZenEnginer I am getting the following error

invalid type: null, expected a string

My initial suspicion is this may be an issue with the JSON I output from the editor being a different version than the Engine using that output.

Setup:

I am running the ZenEngine within a NodeJS AWS Lambda.

NodeJS Version: v20.14.0 ZenEngine Version: v0.31.0 Bindings Version: zen-engine-linux-x64-gnu@v0.31.0 Editor: Tested with both https://gorules.github.io/jdm-editor/?path=/story/decision-graph--controlled and https://editor.gorules.io/

Runtime Lambda Code:

  const engine = new ZenEngine();
  console.log(`Rules Service: Executing rule`, ruleFromDb.rule);
  console.log(`Rules Service: Executing rule with input`, executeRule.inputs);

  let decision: ZenDecision;
  try {
    decision = engine.createDecision(ruleFromDb.rule);
  } catch (error) {
    console.log(`Rules Service: Failed to create decision`);
    throw error;
  }

  let result: ZenEngineResponse;
  try {
    result = await decision.evaluate(executeRule.inputs);
  } catch (error) {
    console.log(`Rules Service: Failed to evaluate decision `);
    throw error;
  }

decision = engine.createDecision(ruleFromDb.rule); is the line that throws an error.

Decision Context:

The context is loaded into the engine to create a decision

{
  "contentType": "application/vnd.gorules.decision",
  "nodes": [
    {
      "id": "ca98730e-a40f-4601-98cc-b5a57429596d",
      "type": "inputNode",
      "position": {
        "x": 70,
        "y": 250
      },
      "name": "Request"
    },
    {
      "id": "c5e747fe-b74b-4b74-9fd0-bfd7d67007c3",
      "type": "outputNode",
      "position": {
        "x": 670,
        "y": 250
      },
      "name": "Response"
    },
    {
      "id": "359173d8-0068-45f8-bb71-8240ad73201d",
      "type": "decisionTableNode",
      "position": {
        "x": 370,
        "y": 250
      },
      "name": "test",
      "content": {
        "hitPolicy": "first",
        "inputs": [
          {
            "id": "HVo_JpALi8",
            "field": "adjustment",
            "name": "Adjustment Amount"
          },
          {
            "id": "HW6mSVfLbs",
            "field": "lineOfBusiness",
            "name": "Line Of Business"
          }
        ],
        "outputs": [
          {
            "field": "hardStop",
            "id": "3EGDrV0ssV",
            "name": "Hard Stop"
          },
          {
            "id": "7093b9d3-a46a-4807-9a74-05db6b3a833d",
            "field": "softStop",
            "name": "Soft Stop"
          }
        ],
        "rules": [
          {
            "_id": "qMpJEvcau6",
            "_description": "",
            "HVo_JpALi8": "> 10",
            "HW6mSVfLbs": "\"Life\"",
            "3EGDrV0ssV": "\"Limit Exceeded\"",
            "7093b9d3-a46a-4807-9a74-05db6b3a833d": ""
          },
          {
            "_id": "6f138169-074a-4dd0-9a20-8e0833a8a1c4",
            "HVo_JpALi8": "> 10",
            "HW6mSVfLbs": "\"Disability\"",
            "3EGDrV0ssV": "",
            "7093b9d3-a46a-4807-9a74-05db6b3a833d": "\"This adjustment will require UW Review\""
          }
        ]
      }
    }
  ],
  "edges": [
    {
      "id": "1d5f4787-4c86-4ed9-99dc-1a3159f65d89",
      "sourceId": "ca98730e-a40f-4601-98cc-b5a57429596d",
      "type": "edge",
      "targetId": "359173d8-0068-45f8-bb71-8240ad73201d"
    },
    {
      "id": "c5d49d3a-fdfd-4f4b-8838-791cee4d4a55",
      "sourceId": "359173d8-0068-45f8-bb71-8240ad73201d",
      "type": "edge",
      "targetId": "c5e747fe-b74b-4b74-9fd0-bfd7d67007c3"
    }
  ]
}

Rule Inputs (Note: it is erroring out before evaluation)

{
    "adjustment": 11,
    "lineOfBusiness": "Life"
}

Additional Information and Questions:

Thank you for any guidance you may be able to provide.

stefan-gorules commented 3 hours ago

Hi @raymondKelly,

Thanks for the praise, and we are definitely open to some contribution. Currently we have few active areas:

Posted issue

Regarding your issue posted above. I am unable to replicate it. This is how my script looks like:

import { ZenEngine } from '@gorules/zen-engine';

const data = {
  "contentType": "application/vnd.gorules.decision",
  "nodes": [
    {
      "id": "ca98730e-a40f-4601-98cc-b5a57429596d",
      "type": "inputNode",
      "position": {
        "x": 70,
        "y": 250
      },
      "name": "Request"
    },
    {
      "id": "c5e747fe-b74b-4b74-9fd0-bfd7d67007c3",
      "type": "outputNode",
      "position": {
        "x": 670,
        "y": 250
      },
      "name": "Response"
    },
    {
      "id": "359173d8-0068-45f8-bb71-8240ad73201d",
      "type": "decisionTableNode",
      "position": {
        "x": 370,
        "y": 250
      },
      "name": "test",
      "content": {
        "hitPolicy": "first",
        "inputs": [
          {
            "id": "HVo_JpALi8",
            "field": "adjustment",
            "name": "Adjustment Amount"
          },
          {
            "id": "HW6mSVfLbs",
            "field": "lineOfBusiness",
            "name": "Line Of Business"
          }
        ],
        "outputs": [
          {
            "field": "hardStop",
            "id": "3EGDrV0ssV",
            "name": "Hard Stop"
          },
          {
            "id": "7093b9d3-a46a-4807-9a74-05db6b3a833d",
            "field": "softStop",
            "name": "Soft Stop"
          }
        ],
        "rules": [
          {
            "_id": "qMpJEvcau6",
            "_description": "",
            "HVo_JpALi8": "> 10",
            "HW6mSVfLbs": "\"Life\"",
            "3EGDrV0ssV": "\"Limit Exceeded\"",
            "7093b9d3-a46a-4807-9a74-05db6b3a833d": ""
          },
          {
            "_id": "6f138169-074a-4dd0-9a20-8e0833a8a1c4",
            "HVo_JpALi8": "> 10",
            "HW6mSVfLbs": "\"Disability\"",
            "3EGDrV0ssV": "",
            "7093b9d3-a46a-4807-9a74-05db6b3a833d": "\"This adjustment will require UW Review\""
          }
        ]
      }
    }
  ],
  "edges": [
    {
      "id": "1d5f4787-4c86-4ed9-99dc-1a3159f65d89",
      "sourceId": "ca98730e-a40f-4601-98cc-b5a57429596d",
      "type": "edge",
      "targetId": "359173d8-0068-45f8-bb71-8240ad73201d"
    },
    {
      "id": "c5d49d3a-fdfd-4f4b-8838-791cee4d4a55",
      "sourceId": "359173d8-0068-45f8-bb71-8240ad73201d",
      "type": "edge",
      "targetId": "c5e747fe-b74b-4b74-9fd0-bfd7d67007c3"
    }
  ]
};

const engine = new ZenEngine();
const decision = engine.createDecision(data);

console.log(await decision.evaluate({
    "adjustment": 11,
    "lineOfBusiness": "Life"
  }
))

The output is:

{
  performance: "702.333µs",
  result: {
    hardStop: "Limit Exceeded",
  },
}

It's executed using bun script.mts command, but you can use Node.js or deno as well. The likeliest scenario in your example is that ruleFromDb.rule is null (or you might have to do Buffer.from(ruleFromDb.rule) if type is string). The createDecision function either accepts an object or a Buffer.

Additional questions

raymondKelly commented 3 hours ago

@stefan-gorules Thank you for the fast response! Your line of debugging was spot on and something I completely missed. The issue appears to be here for me

                            {
                                "0f59cb62-70eb-41d6-943c-53a3c6e6fcbd": "\"Limit Exceeded\"",
                                "_id": "a7708509-8bde-4898-af8a-9d70b44f1308",
                                "cb98a62e-d1db-4ef5-ace1-05a5c03173b1": "\"Life\"",
                                "e0a33147-5c19-4bb8-92ff-6652f83ef3f5": null,
                                "fb8cb988-e4c9-427f-9184-000b08bbafdb": "> 10"
                            },
                            {
                                "0f59cb62-70eb-41d6-943c-53a3c6e6fcbd": null,
                                "_id": "baa31220-61e1-437a-ad69-de999216a3be",
                                "cb98a62e-d1db-4ef5-ace1-05a5c03173b1": "\"Disability\"",
                                "e0a33147-5c19-4bb8-92ff-6652f83ef3f5": "\"This adjustment will require UW Review\"",
                                "fb8cb988-e4c9-427f-9184-000b08bbafdb": "> 10"
                            }
                        ]

The empty strings are somehow being turned into null somewhere in my stack. I suspect DynamoDB may be doing this. Thank you again this was extremely helpful and sorry for any time wasted.

As for contributions. In my personal time I am contributing to Prisma ORM with a new serverless adapter but once done with that I would be happy to help with this project, especially since I would like to get more involved with rust code.