From 68fb88d775fed11427c52104bc612a081a17d989 Mon Sep 17 00:00:00 2001 From: hibutwhy <112614297+hibutwhy@users.noreply.github.com> Date: Mon, 24 Aug 2026 03:33:14 -0700 Subject: [PATCH] fix: handle null message objects in spectral JSONPath filters When a payload property is named message and has a null value in an example, JSONPath filter expressions that check @.schemaFormat crash with a TypeError. Added !@null guards matching the pattern already used by other paths in the same ruleset. Fixes #863 --- .../messageExamples-spectral-rule-v2.ts | 4 +-- packages/parser/src/ruleset/v2/ruleset.ts | 16 ++++----- .../v2/asyncapi2-message-examples.spec.ts | 33 +++++++++++++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/parser/src/ruleset/v2/functions/messageExamples-spectral-rule-v2.ts b/packages/parser/src/ruleset/v2/functions/messageExamples-spectral-rule-v2.ts index 2d964762b..e41187032 100644 --- a/packages/parser/src/ruleset/v2/functions/messageExamples-spectral-rule-v2.ts +++ b/packages/parser/src/ruleset/v2/functions/messageExamples-spectral-rule-v2.ts @@ -17,9 +17,9 @@ export function asyncApi2MessageExamplesParserRule(parser: Parser): RuleDefiniti recommended: true, given: [ // messages - '$.channels.*.[publish,subscribe][?(@property === \'message\' && @.schemaFormat !== void 0)]', + '$.channels.*.[publish,subscribe][?(@property === \'message\' && !@null && @.schemaFormat !== void 0)]', '$.channels.*.[publish,subscribe].message.oneOf[?(!@null && @.schemaFormat !== void 0)]', - '$.components.channels.*.[publish,subscribe].message[?(@property === \'message\' && @.schemaFormat !== void 0)]', + '$.components.channels.*.[publish,subscribe].message[?(@property === \'message\' && !@null && @.schemaFormat !== void 0)]', '$.components.channels.*.[publish,subscribe].message.oneOf[?(!@null && @.schemaFormat !== void 0)]', '$.components.messages[?(!@null && @.schemaFormat !== void 0)]', // message traits diff --git a/packages/parser/src/ruleset/v2/ruleset.ts b/packages/parser/src/ruleset/v2/ruleset.ts index b03a7549f..2a0ff1c9b 100644 --- a/packages/parser/src/ruleset/v2/ruleset.ts +++ b/packages/parser/src/ruleset/v2/ruleset.ts @@ -123,9 +123,9 @@ export const v2CoreRuleset = { recommended: true, given: [ // messages - '$.channels.*.[publish,subscribe][?(@property === \'message\' && @.schemaFormat === void 0)]', + '$.channels.*.[publish,subscribe][?(@property === \'message\' && !@null && @.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][?(@property === \'message\' && !@null && @.schemaFormat === void 0)]', '$.components.channels.*.[publish,subscribe].message.oneOf[?(!@null && @.schemaFormat === void 0)]', '$.components.messages[?(!@null && @.schemaFormat === void 0)]', // message traits @@ -201,9 +201,9 @@ export const v2SchemasRuleset = (parser: Parser) => { severity: 'error', recommended: true, given: [ - '$.channels[*][publish,subscribe][?(@property === \'message\' && @.schemaFormat === void 0)].payload.default^', + '$.channels[*][publish,subscribe][?(@property === \'message\' && !@null && @.schemaFormat === void 0)].payload.default^', '$.channels.*.parameters.*.schema.default^', - '$.components.channels[*][publish,subscribe][?(@property === \'message\' && @.schemaFormat === void 0)].payload.default^', + '$.components.channels[*][publish,subscribe][?(@property === \'message\' && !@null && @.schemaFormat === void 0)].payload.default^', '$.components.channels.*.parameters.*.schema.default^', '$.components.schemas.*.default^', '$.components.parameters.*.schema.default^', @@ -223,9 +223,9 @@ export const v2SchemasRuleset = (parser: Parser) => { severity: 'error', recommended: true, given: [ - '$.channels[*][publish,subscribe][?(@property === \'message\' && @.schemaFormat === void 0)].payload.examples^', + '$.channels[*][publish,subscribe][?(@property === \'message\' && !@null && @.schemaFormat === void 0)].payload.examples^', '$.channels.*.parameters.*.schema.examples^', - '$.components.channels[*][publish,subscribe][?(@property === \'message\' && @.schemaFormat === void 0)].payload.examples^', + '$.components.channels[*][publish,subscribe][?(@property === \'message\' && !@null && @.schemaFormat === void 0)].payload.examples^', '$.components.channels.*.parameters.*.schema.examples^', '$.components.schemas.*.examples^', '$.components.parameters.*.schema.examples^', @@ -338,9 +338,9 @@ export const v2RecommendedRuleset = { recommended: true, formats: AsyncAPIFormats.filterByMajorVersions(['2']).excludeByVersions(['2.0.0', '2.1.0', '2.2.0', '2.3.0']).formats(), // message.messageId is available starting from v2.4. given: [ - '$.channels.*.[publish,subscribe][?(@property === "message" && @.oneOf == void 0)]', + '$.channels.*.[publish,subscribe][?(@property === "message" && !@null && @.oneOf == void 0)]', '$.channels.*.[publish,subscribe].message.oneOf.*', - '$.components.channels.*.[publish,subscribe][?(@property === "message" && @.oneOf == void 0)]', + '$.components.channels.*.[publish,subscribe][?(@property === "message" && !@null && @.oneOf == void 0)]', '$.components.channels.*.[publish,subscribe].message.oneOf.*', '$.components.messages.*', ], diff --git a/packages/parser/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts b/packages/parser/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts index f6229b6a1..81f218f58 100644 --- a/packages/parser/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts +++ b/packages/parser/test/ruleset/rules/v2/asyncapi2-message-examples.spec.ts @@ -477,4 +477,37 @@ testRule('asyncapi2-message-examples', [ }, ], }, + + { + name: 'valid case (payload property named "message" with null value in example)', + document: { + asyncapi: '2.6.0', + channels: { + someChannel: { + publish: { + message: { + payload: { + type: 'object', + required: ['message'], + properties: { + message: { + type: ['string', 'null'], + maxLength: 50, + }, + }, + }, + examples: [ + { + payload: { + message: null, + }, + }, + ], + }, + }, + }, + }, + }, + errors: [], + }, ]);