Repository: asyncapi/parser-js
Pull request #720 (2023-02-07 16:32:49)

feat: validate unparsed AsyncAPI document
<!--   Thank you for your contribution. Before you submit the pull request:
1. Follow contributing guidelines and make sure PR title follows Conventional Commits specification -> https://github.com/asyncapi/generator/blob/master/CONTRIBUTING.md
2. Test your changes and attach their results to the pull request.
3. Update the relevant documentation.
-->

**Description**

Validate unparsed AsyncAPI document (before `$ref`s resolving).

magicmatatjahu: @jonaslagoni Could you check?
magicmatatjahu: @jonaslagoni 

> I guess I dont understand why we don't change the parsing logic to validate before resolving references, and then again after? This fix looks like an incorrect approach to take 🤔 Or is there some underlying reason?

Spectral resolver (and other json-schema resolvers) doesn't validate the json-schema but only resolve it. Also AJV try to validate the json-schema based on json-schema and that's the problem. After referencing we don't know if `$ref` in given place can be located and/or given ref has valid object (for given location, e.g. message etc.). So we exactly validate two things: unresolved AsyncAPI document with refs and with resolved refs (even that deep). I don't know how to make it in different way. That PR is related to the https://github.com/asyncapi/parser-js/issues/405
jonaslagoni: Ahh shit, validation happens through spectral now, yea then you cant do a simple double validation once before refs are resolved and once after...

magicmatatjahu: @jonaslagoni 

> Ahh shit, validation happens through spectral now, yea then you can't do a simple double validation once before refs are resolved and once after...

this is what I do 😄 
magicmatatjahu: /rtm

Patch:
diff --git a/src/ruleset/ruleset.ts b/src/ruleset/ruleset.ts
index d088bd9..0f87c3d 100644
--- a/src/ruleset/ruleset.ts
+++ b/src/ruleset/ruleset.ts
@@ -53,21 +53,20 @@ export const coreRuleset = {
         },
       },
     },
-    // enable after fixing https://github.com/asyncapi/spec-json-schemas/issues/296
-    // 'asyncapi-document-unresolved': {
-    //   description: 'Checking if the AsyncAPI document has valid unresolved structure.',
-    //   message: '{{error}}',
-    //   severity: 'error',
-    //   recommended: true,
-    //   resolved: false,
-    //   given: '$',
-    //   then: {
-    //     function: documentStructure,
-    //     functionOptions: {
-    //       resolved: false,
-    //     },
-    //   },
-    // },
+    'asyncapi-document-unresolved': {
+      description: 'Checking if the AsyncAPI document has valid unresolved structure.',
+      message: '{{error}}',
+      severity: 'error',
+      recommended: true,
+      resolved: false,
+      given: '$',
+      then: {
+        function: documentStructure,
+        functionOptions: {
+          resolved: false,
+        },
+      },
+    },
     'asyncapi-internal': {
       description: 'That rule is internal to extend Spectral functionality for Parser purposes.',
       recommended: true,


Test patch:
diff --git a/test/ruleset/rules/asyncapi-document-unresolved.spec.ts b/test/ruleset/rules/asyncapi-document-unresolved.spec.ts
index d2242b7..7787605 100644
--- a/test/ruleset/rules/asyncapi-document-unresolved.spec.ts
+++ b/test/ruleset/rules/asyncapi-document-unresolved.spec.ts
@@ -1,90 +1,85 @@
-// import { testRule, DiagnosticSeverity } from '../tester';
+import { testRule, DiagnosticSeverity } from '../tester';
 
-describe('asyncapi-document-unresolved', () => {
-  it.todo('add tests after fixing https://github.com/asyncapi/spec-json-schemas/issues/296');
-});
+testRule('asyncapi-document-unresolved', [
+  {
+    name: 'valid case',
+    document: {
+      asyncapi: '2.0.0',
+      info: {
+        title: 'Valid AsyncApi document',
+        version: '1.0',
+      },
+      channels: {
+        someChannel: {
+          publish: {
+            message: {
+              $ref: '#/components/messages/someMessage',
+            },
+          },
+        },
+      },
+      components: {
+        messages: {
+          someMessage: {},
+        },
+      },
+    },
+    errors: [],
+  },
 
-// 
-// testRule('asyncapi-document-unresolved', [
-//   {
-//     name: 'valid case',
-//     document: {
-//       asyncapi: '2.0.0',
-//       info: {
-//         title: 'Valid AsyncApi document',
-//         version: '1.0',
-//       },
-//       channels: {
-//         someChannel: {
-//           publish: {
-//             message: {
-//               $ref: '#/components/messages/someMessage',
-//             },
-//           },
-//         },
-//       },
-//       components: {
-//         messages: {
-//           someMessage: {},
-//         },
-//       },
-//     },
-//     errors: [],
-//   },
+  {
+    name: 'invalid case (reference for operation object is not allowed)',
+    document: {
+      asyncapi: '2.0.0',
+      info: {
+        title: 'Valid AsyncApi document',
+        version: '1.0',
+      },
+      channels: {
+        someChannel: {
+          publish: {
+            $ref: '#/components/x-operations/someOperation',
+          },
+        },
+      },
+      components: {
+        'x-operations': {
+          someOperation: {},
+        },
+      },
+    },
+    errors: [
+      {
+        message: 'Referencing in this place is not allowed',
+        path: ['channels', 'someChannel', 'publish'],
+        severity: DiagnosticSeverity.Error,
+      },
+    ],
+  },
 
-//   {
-//     name: 'invalid case (reference for operation object is not allowed)',
-//     document: {
-//       asyncapi: '2.0.0',
-//       info: {
-//         title: 'Valid AsyncApi document',
-//         version: '1.0',
-//       },
-//       channels: {
-//         someChannel: {
-//           publish: {
-//             $ref: '#/components/x-operations/someOperation',
-//           },
-//         },
-//       },
-//       components: {
-//         'x-operations': {
-//           someOperation: {},
-//         },
-//       },
-//     },
-//     errors: [
-//       {
-//         message: 'Referencing in this place is not allowed',
-//         path: ['channels', 'someChannel', 'publish'],
-//         severity: DiagnosticSeverity.Error,
-//       },
-//     ],
-//   },
-
-//   {
-//     name: 'invalid case (case when other errors should also occur but we filter them out - required info field is omitted)',
-//     document: {
-//       asyncapi: '2.0.0',
-//       channels: {
-//         someChannel: {
-//           publish: {
-//             $ref: '#/components/x-operations/someOperation',
-//           },
-//         },
-//       },
-//       components: {
-//         'x-operations': {
-//           someOperation: {},
-//         },
-//       },
-//     },
-//     errors: [
-//       {
-//         message: 'Referencing in this place is not allowed',
-//         path: ['channels', 'someChannel', 'publish'],
-//         severity: DiagnosticSeverity.Error,
-//       },
-//     ],
-//   },
-// ]);
+  {
+    name: 'invalid case (case when other errors should also occur but we filter them out - required info field is omitted)',
+    document: {
+      asyncapi: '2.0.0',
+      channels: {
+        someChannel: {
+          publish: {
+            $ref: '#/components/x-operations/someOperation',
+          },
+        },
+      },
+      components: {
+        'x-operations': {
+          someOperation: {},
+        },
+      },
+    },
+    errors: [
+      {
+        message: 'Referencing in this place is not allowed',
+        path: ['channels', 'someChannel', 'publish'],
+        severity: DiagnosticSeverity.Error,
+      },
+    ],
+  },
+]);