Repository: asyncapi/parser-js (ts)

Issue:
Local avro schema with nullable fields fails with `Cannot read properties of null (reading 'schemaFormat')` using `@asyncapi/parser@v2.0.2`
**TL;DR;**
AVRO schema with nullable fields works with `@asyncapi/parser@v2.0.1` but fails when I use `@asyncapi/parser@v2.0.2`

#### Describe the bug

If I my AVRO schema has a nullable field, e.g. `default: null, type: ["null", "string"]`, then it fails with a unclear error when I try to generate a model or when I validate it by doing `npx -p @asyncapi/cli@v0.47.9 asyncapi validate example.yaml`. It works fine and I can generate a HTML doc if I lock down the `@asyncapi/parser` to version `2.0.1` version by doing `npx -p @asyncapi/cli@v0.47.5 -p @asyncapi/parser@v2.0.1 -p @asyncapi/generator@v1.10.4 asyncapi validate example.yaml`.

Generate command:
> Generator Error: Input is not a corrent AsyncAPI document so it cannot be processed.

Validate command:
> File example.yaml and/or referenced documents have governance issues.

If I remote `default: null` from the schema then it can generate the documentation but the schema will not allow the producer to not set the field.

I debugged the code and found that there is a `TypeError: Cannot read properties of null (reading 'schemaFormat')` error that gets discarded which seems to be triggered by [this rule](https://github.com/asyncapi/parser-js/blob/master/src/ruleset/v2/ruleset.ts#L126)

```
TypeError: Cannot read properties of null (reading 'schemaFormat')
    at Object.$.channels.*.[publish,subscribe].message.oneOf[?(@.schemaFormat === void 0)] (eval at query (/Users/<REDACTED>/.npm/_npx/c7940f4513e4e66f/node_modules/nimma/dist/legacy/cjs/core/index.js:66:71), <anonymous>:191:31)
    at eval (eval at query (/Users/<REDACTED>/.npm/_npx/c7940f4513e4e66f/node_modules/nimma/dist/legacy/cjs/core/index.js:66:71), <anonymous>:641:91)
```

#### How to Reproduce

- 🔴 `npx -p @asyncapi/cli@v0.47.9 asyncapi validate example.yaml`
- 🔴 `npx -p @asyncapi/cli@v0.47.5 -p @asyncapi/parser@v2.0.2 -p @asyncapi/generator@v1.10.4 asyncapi validate example.yaml`
- 🟢 `npx -p @asyncapi/cli@v0.47.5 -p @asyncapi/parser@v2.0.1 -p @asyncapi/generator@v1.10.4 asyncapi validate example.yaml`

```
> npx -p @asyncapi/cli@v0.47.9 asyncapi validate example.yaml

File example.yaml and/or referenced documents have governance issues.

> npx -p @asyncapi/cli@v0.47.5 -p @asyncapi/parser@v2.0.2 -p @asyncapi/generator@v1.10.4 asyncapi validate example.yaml

File example.yaml and/or referenced documents have governance issues.

> npx -p @asyncapi/cli@v0.47.5 -p @asyncapi/parser@v2.0.1 -p @asyncapi/generator@v1.10.4 asyncapi validate example.yaml

File kafka-topics.yaml is valid! File kafka-topics.yaml and referenced documents don't have governance issues.
```

<details>

<summary>AsyncAPI file:</summary>

```yaml
asyncapi: 2.6.0
id: https://example.com
tags: []
info:
  title: example
  version: "1.0.0"
  license:
    name: No License
  contact:
    name: Foo
    email: foo@example.com
  description: >
    Short description

channels:
  public.ad-moderation.ad:
    publish:
      operationId: test1
      message:
        messageId: test2
        schemaFormat: "application/vnd.apache.avro;version=1.9.0"
        payload:
          $ref: ./example1.avsc.yaml
```

</details>

<details>

<summary>AVRO schema:</summary>

```yaml
name: Command
type: record
fields:
  - name: foo
    # Works if you add a comment away `default: null`.
    default: null
    type:
      - "null"
      - string
```

</details>


#### Expected behavior

`asyncapi validate` should return OK and `asyncapi generate [...]` should generate a HTML documentation.

Pull request description:
fix: null check when checking schemaFormat in array items
**Description**

due to a quirk of how nimma generates the evaluation logic for checking fields in objects inside an array whilst filtering it is possible to get an npe. Adding specific checks against this.

**Related issue(s)**
Fixes #784

Patch:
diff --git a/src/ruleset/v2/ruleset.ts b/src/ruleset/v2/ruleset.ts
index a2e7816..419a6a0 100644
--- a/src/ruleset/v2/ruleset.ts
+++ b/src/ruleset/v2/ruleset.ts
@@ -123,17 +123,17 @@ export const v2CoreRuleset = {
       given: [
         // messages
         '$.channels.*.[publish,subscribe].[?(@property === \'message\' && @.schemaFormat === void 0)]',
-        '$.channels.*.[publish,subscribe].message.oneOf[?(@.schemaFormat === void 0)]',
+        '$.channels.*.[publish,subscribe].message.oneOf[?(!@null && @.schemaFormat === void 0)]',
         '$.components.channels.*.[publish,subscribe].[?(@property === \'message\' && @.schemaFormat === void 0)]',
-        '$.components.channels.*.[publish,subscribe].message.oneOf[?(@.schemaFormat === void 0)]',
-        '$.components.messages[?(@.schemaFormat === void 0)]',
+        '$.components.channels.*.[publish,subscribe].message.oneOf[?(!@null && @.schemaFormat === void 0)]',
+        '$.components.messages[?(!@null && @.schemaFormat === void 0)]',
         // message traits
-        '$.channels.*.[publish,subscribe].message.traits[?(@.schemaFormat === void 0)]',
-        '$.channels.*.[publish,subscribe].message.oneOf.*.traits[?(@.schemaFormat === void 0)]',
-        '$.components.channels.*.[publish,subscribe].message.traits[?(@.schemaFormat === void 0)]',
-        '$.components.channels.*.[publish,subscribe].message.oneOf.*.traits[?(@.schemaFormat === void 0)]',
-        '$.components.messages.*.traits[?(@.schemaFormat === void 0)]',
-        '$.components.messageTraits[?(@.schemaFormat === void 0)]',
+        '$.channels.*.[publish,subscribe].message.traits[?(!@null && @.schemaFormat === void 0)]',
+        '$.channels.*.[publish,subscribe].message.oneOf.*.traits[?(!@null && @.schemaFormat === void 0)]',
+        '$.components.channels.*.[publish,subscribe].message.traits[?(!@null && @.schemaFormat === void 0)]',
+        '$.components.channels.*.[publish,subscribe].message.oneOf.*.traits[?(!@null && @.schemaFormat === void 0)]',
+        '$.components.messages.*.traits[?(!@null && @.schemaFormat === void 0)]',
+        '$.components.messageTraits[?(!@null && @.schemaFormat === void 0)]',
       ],
       then: {
         function: messageExamples,


Test patch:
diff --git a/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts b/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts
index aa3f638..d8d2ff0 100644
--- a/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts
+++ b/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts
@@ -360,6 +360,7 @@ testRule('asyncapi2-message-examples', [
     },
     errors: [],
   },
+
   {
     name: 'invalid avro spec case',
     document: {
@@ -391,5 +392,87 @@ testRule('asyncapi2-message-examples', [
       },
     },
     errors: [],
-  }
+  },
+
+  {
+    name: 'avro can contain null values',
+    document: {
+      asyncapi: '2.6.0',
+      channels: {
+        someChannel: {
+          publish: {
+            message: {
+              schemaFormat: 'application/vnd.apache.avro;version=1.9.0',
+              payload: {
+                type: 'record',
+                name: 'Command',
+                fields: [{
+                  name: 'foo',
+                  default: null,
+                  type: ['null', 'string'],
+                }],
+              },
+              examples: [
+                {
+                  payload: {}
+                },
+              ],
+            },
+          },
+        },
+      },
+    },
+    errors: [],
+  },
+
+  {
+    name: 'handles oneOf processing',
+    document: {
+      asyncapi: '2.6.0',
+      channels: {
+        someChannel: {
+          publish: {
+            message: {
+              oneOf: [
+                {
+                  schemaFormat: 'application/vnd.apache.avro;version=1.9.0',
+                  payload: {
+                    type: 'record',
+                    name: 'Command',
+                    fields: [{
+                      name: 'foo',
+                      default: null,
+                      type: ['null', 'string'],
+                    }],
+                  },
+                  examples: [
+                    {
+                      payload: {}
+                    },
+                  ],
+                },
+                {
+                  payload: {
+                    type: 'string'
+                  },
+                  examples: [
+                    {
+                      payload: 1
+                    },
+                  ],
+                },
+              ],
+            },
+          },
+        },
+      },
+    },
+    errors: [
+      {
+        message: '"payload" property type must be string',
+        path: ['channels', 'someChannel', 'publish', 'message', 'oneOf', '1', 'examples', '0', 'payload'],
+        severity: DiagnosticSeverity.Error,
+      },
+    ],
+  },
 ]);
diff --git a/test/ruleset/tester.ts b/test/ruleset/tester.ts
index fb8a0b0..7924e36 100644
--- a/test/ruleset/tester.ts
+++ b/test/ruleset/tester.ts
@@ -31,6 +31,7 @@ export function testRule(ruleName: RuleNames, tests: Scenario,): void {
         const doc = JSON.stringify(testCase.document);
 
         const errors = await parser.validate(doc);
+        expect(errors.filter(({ code }) => code === 'uncaught-error')).toHaveLength(0);
         expect(errors.filter(({ code }) => code === ruleName)).toEqual(
           testCase.errors.map(error => expect.objectContaining(error) as unknown),
         );