@amritk/lint 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/AI.md +73 -4
  2. package/README.md +65 -14
  3. package/dist/core/filter-expression.js +17 -4
  4. package/dist/core/filter.js +82 -35
  5. package/dist/core/glob.js +25 -31
  6. package/dist/core/jsonpath.d.ts +9 -1
  7. package/dist/core/jsonpath.js +74 -16
  8. package/dist/core/lint.d.ts +8 -1
  9. package/dist/core/lint.js +4 -18
  10. package/dist/core/order.d.ts +30 -0
  11. package/dist/core/order.js +36 -0
  12. package/dist/core/own-key.d.ts +18 -0
  13. package/dist/core/own-key.js +12 -0
  14. package/dist/core/pointers.d.ts +3 -2
  15. package/dist/core/pointers.js +27 -18
  16. package/dist/core/ruleset.js +74 -27
  17. package/dist/core/runner.js +16 -23
  18. package/dist/core/severity.d.ts +11 -0
  19. package/dist/core/severity.js +13 -0
  20. package/dist/fix/apply.js +9 -2
  21. package/dist/fix/plugin.js +2 -1
  22. package/dist/functions/alphabetical.d.ts +14 -0
  23. package/dist/functions/alphabetical.js +4 -3
  24. package/dist/functions/or.js +1 -1
  25. package/dist/functions/ref-index.d.ts +15 -0
  26. package/dist/functions/ref-index.js +33 -0
  27. package/dist/functions/schema.d.ts +15 -0
  28. package/dist/functions/schema.js +2 -0
  29. package/dist/functions/typed-enum.js +2 -1
  30. package/dist/functions/unreferenced-reusable-object.js +3 -20
  31. package/dist/functions/xor.js +1 -1
  32. package/dist/index.d.ts +16 -16
  33. package/dist/index.js +25 -94
  34. package/dist/rules/asyncapi/asyncapi.d.ts +3 -0
  35. package/dist/rules/asyncapi/asyncapi.js +563 -0
  36. package/dist/rules/asyncapi/formats.d.ts +23 -0
  37. package/dist/rules/asyncapi/formats.js +49 -0
  38. package/dist/rules/asyncapi/functions/aas-server-variables.d.ts +8 -0
  39. package/dist/rules/asyncapi/functions/aas-server-variables.js +5 -0
  40. package/dist/rules/asyncapi/functions/aas-tags-unique.d.ts +2 -0
  41. package/dist/rules/asyncapi/functions/aas-tags-unique.js +5 -0
  42. package/dist/rules/asyncapi/functions/asyncapi-channel-parameters.d.ts +7 -0
  43. package/dist/rules/asyncapi/functions/asyncapi-channel-parameters.js +46 -0
  44. package/dist/rules/asyncapi/functions/asyncapi-channel-servers.d.ts +27 -0
  45. package/dist/rules/asyncapi/functions/asyncapi-channel-servers.js +61 -0
  46. package/dist/rules/asyncapi/functions/asyncapi-document-schema.d.ts +17 -0
  47. package/dist/rules/asyncapi/functions/asyncapi-document-schema.js +12 -0
  48. package/dist/rules/asyncapi/functions/asyncapi-headers-object.d.ts +23 -0
  49. package/dist/rules/asyncapi/functions/asyncapi-headers-object.js +25 -0
  50. package/dist/rules/asyncapi/functions/asyncapi-message-examples.d.ts +8 -0
  51. package/dist/rules/asyncapi/functions/asyncapi-message-examples.js +42 -0
  52. package/dist/rules/asyncapi/functions/asyncapi-message-id-unique.d.ts +3 -0
  53. package/dist/rules/asyncapi/functions/asyncapi-message-id-unique.js +30 -0
  54. package/dist/rules/asyncapi/functions/asyncapi-operation-id-unique.d.ts +3 -0
  55. package/dist/rules/asyncapi/functions/asyncapi-operation-id-unique.js +30 -0
  56. package/dist/rules/asyncapi/functions/asyncapi-payload.d.ts +14 -0
  57. package/dist/rules/asyncapi/functions/asyncapi-payload.js +32 -0
  58. package/dist/rules/asyncapi/functions/asyncapi-schema-validation.d.ts +11 -0
  59. package/dist/rules/asyncapi/functions/asyncapi-schema-validation.js +17 -0
  60. package/dist/rules/asyncapi/functions/asyncapi-security.d.ts +22 -0
  61. package/dist/rules/asyncapi/functions/asyncapi-security.js +78 -0
  62. package/dist/rules/asyncapi/functions/helpers.d.ts +51 -0
  63. package/dist/rules/asyncapi/functions/helpers.js +91 -0
  64. package/dist/rules/asyncapi/functions/index.d.ts +15 -0
  65. package/dist/rules/asyncapi/functions/index.js +53 -0
  66. package/dist/rules/asyncapi/functions/pointer.d.ts +11 -0
  67. package/dist/rules/asyncapi/functions/pointer.js +11 -0
  68. package/dist/rules/asyncapi/functions/schema-format.d.ts +4 -0
  69. package/dist/rules/asyncapi/functions/schema-format.js +7 -0
  70. package/dist/rules/asyncapi/index.d.ts +33 -0
  71. package/dist/rules/asyncapi/index.js +60 -0
  72. package/dist/rules/asyncapi/schemas/aas20.d.ts +2 -0
  73. package/dist/rules/asyncapi/schemas/aas20.js +4 -0
  74. package/dist/rules/asyncapi/schemas/aas21.d.ts +2 -0
  75. package/dist/rules/asyncapi/schemas/aas21.js +4 -0
  76. package/dist/rules/asyncapi/schemas/aas22.d.ts +2 -0
  77. package/dist/rules/asyncapi/schemas/aas22.js +4 -0
  78. package/dist/rules/asyncapi/schemas/aas23.d.ts +2 -0
  79. package/dist/rules/asyncapi/schemas/aas23.js +4 -0
  80. package/dist/rules/asyncapi/schemas/aas24.d.ts +2 -0
  81. package/dist/rules/asyncapi/schemas/aas24.js +4 -0
  82. package/dist/rules/asyncapi/schemas/aas25.d.ts +2 -0
  83. package/dist/rules/asyncapi/schemas/aas25.js +4 -0
  84. package/dist/rules/asyncapi/schemas/aas26.d.ts +2 -0
  85. package/dist/rules/asyncapi/schemas/aas26.js +4 -0
  86. package/dist/rules/asyncapi/schemas/aas30.d.ts +2 -0
  87. package/dist/rules/asyncapi/schemas/aas30.js +4 -0
  88. package/dist/rules/asyncapi/schemas/index.d.ts +20 -0
  89. package/dist/rules/asyncapi/schemas/index.js +45 -0
  90. package/dist/rules/openapi/fixers.d.ts +6 -2
  91. package/dist/rules/openapi/fixers.js +9 -6
  92. package/dist/rules/openapi/functions/oas-discriminator.js +1 -1
  93. package/dist/rules/openapi/functions/oas-mutually-exclusive.js +1 -1
  94. package/dist/rules/openapi/functions/oas-op-security-defined.js +1 -1
  95. package/dist/rules/openapi/functions/oas-path-param.js +5 -4
  96. package/dist/rules/openapi/functions/oas-server-variables.d.ts +6 -6
  97. package/dist/rules/openapi/functions/oas-server-variables.js +2 -45
  98. package/dist/rules/openapi/functions/oas-tags-unique.d.ts +2 -3
  99. package/dist/rules/openapi/functions/oas-tags-unique.js +2 -16
  100. package/dist/rules/openapi/functions/oas-unused-component.js +3 -25
  101. package/dist/rules/openapi/index.d.ts +9 -2
  102. package/dist/rules/openapi/index.js +15 -70
  103. package/dist/rules/openapi/schemas/index.js +5 -1
  104. package/dist/rules/shared/helpers.d.ts +2 -0
  105. package/dist/rules/shared/helpers.js +4 -0
  106. package/dist/rules/shared/server-variables.d.ts +27 -0
  107. package/dist/rules/shared/server-variables.js +55 -0
  108. package/dist/rules/shared/tags-unique.d.ts +7 -0
  109. package/dist/rules/shared/tags-unique.js +19 -0
  110. package/dist/ruleset-files.d.ts +47 -0
  111. package/dist/ruleset-files.js +92 -0
  112. package/package.json +9 -5
@@ -0,0 +1,46 @@
1
+ import { isObject, parseUrlVariables } from "./helpers.js";
2
+ const channelAddress = (channel, path, document) => {
3
+ const declaredAddress = channel["address"];
4
+ if (typeof declaredAddress === "string")
5
+ return declaredAddress;
6
+ const version = isObject(document) ? document["asyncapi"] : void 0;
7
+ if (typeof version !== "string" || !version.startsWith("2."))
8
+ return void 0;
9
+ if (path.length !== 2 || path[0] !== "channels")
10
+ return void 0;
11
+ return typeof path[1] === "string" ? path[1] : void 0;
12
+ };
13
+ const asyncApiChannelParameters = (channel, _options, context) => {
14
+ if (!isObject(channel))
15
+ return [];
16
+ if (typeof channel["$ref"] === "string")
17
+ return [];
18
+ const declaredParameters = channel["parameters"];
19
+ if (declaredParameters !== void 0 && !isObject(declaredParameters))
20
+ return [];
21
+ const parameters = isObject(declaredParameters) ? declaredParameters : {};
22
+ const address = channelAddress(channel, context.path, context.document.data);
23
+ if (address === void 0)
24
+ return [];
25
+ const declared = new Set(parseUrlVariables(address));
26
+ const results = [];
27
+ const missing = [...declared].filter((name) => !Object.hasOwn(parameters, name));
28
+ if (missing.length > 0) {
29
+ results.push({
30
+ message: `Channel parameters must be described: ${missing.join(", ")}`,
31
+ path: [...context.path, "parameters"]
32
+ });
33
+ }
34
+ for (const name of Object.keys(parameters)) {
35
+ if (!declared.has(name)) {
36
+ results.push({
37
+ message: `Channel parameter "${name}" is not used in the channel address`,
38
+ path: [...context.path, "parameters", name]
39
+ });
40
+ }
41
+ }
42
+ return results;
43
+ };
44
+ export {
45
+ asyncApiChannelParameters
46
+ };
@@ -0,0 +1,27 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /**
3
+ * Checks that every server a channel lists is one the document declares.
4
+ *
5
+ * The spec scopes this to where the channel is written, and the two cases differ
6
+ * (AsyncAPI 3.0, Channel Object `servers`): a channel in the **root** Channels
7
+ * Object "MUST point to a subset of server definitions located in the root
8
+ * Servers Object, and MUST NOT point to … the Components Object or anywhere
9
+ * else", while a channel in the Components Object "MAY point to a Server Object
10
+ * in any location".
11
+ *
12
+ * The carve-out excuses only references pointing *outside* `#/servers`, so it
13
+ * suspends one of the two checks and not the other. A reusable 3.0 channel
14
+ * pointing at `#/components/servers/staging` is permitted and must stay silent;
15
+ * one pointing at `#/servers/nope` still names the root Servers Object and must
16
+ * still exist there. Skipping reusable channels altogether lost that second
17
+ * check, and reporting them lost the first.
18
+ *
19
+ * 2.x has no carve-out at all — a channel's `servers` is a list of names from
20
+ * the root Servers Object wherever the channel is written — so both checks apply
21
+ * to both locations there.
22
+ *
23
+ * Given the whole document (`$`), because the check needs the top-level
24
+ * `servers` map as well as each channel. Runs unresolved, so a 3.0 `$ref` is
25
+ * still a reference rather than the server it points at.
26
+ */
27
+ export declare const asyncApiChannelServers: RulesetFunction;
@@ -0,0 +1,61 @@
1
+ import { isObject } from "./helpers.js";
2
+ import { pointerSegment } from "./pointer.js";
3
+ const SERVERS_REF = "#/servers/";
4
+ const readEntry = (entry) => {
5
+ if (typeof entry === "string")
6
+ return { kind: "named", name: entry, path: [] };
7
+ if (!isObject(entry))
8
+ return void 0;
9
+ const ref = entry["$ref"];
10
+ if (typeof ref !== "string")
11
+ return void 0;
12
+ if (!ref.startsWith(SERVERS_REF))
13
+ return { kind: "elsewhere", ref, path: ["$ref"] };
14
+ return { kind: "named", name: pointerSegment(ref.slice(SERVERS_REF.length)), path: ["$ref"] };
15
+ };
16
+ const asyncApiChannelServers = (document, _options, _context) => {
17
+ if (!isObject(document))
18
+ return [];
19
+ const declared = new Set(isObject(document["servers"]) ? Object.keys(document["servers"]) : []);
20
+ const version = document["asyncapi"];
21
+ const isV2 = typeof version === "string" && version.startsWith("2.");
22
+ const components = isObject(document["components"]) ? document["components"] : void 0;
23
+ const roots = [
24
+ { path: ["channels"], channels: document["channels"], mustPointAtRootServers: true },
25
+ // Reusable channels: in 3.0 they may reference a server anywhere, so only the
26
+ // "does it exist" half applies to the ones that do name `#/servers`.
27
+ { path: ["components", "channels"], channels: components?.["channels"], mustPointAtRootServers: isV2 }
28
+ ];
29
+ const results = [];
30
+ for (const root of roots) {
31
+ if (!isObject(root.channels))
32
+ continue;
33
+ for (const [address, channel] of Object.entries(root.channels)) {
34
+ if (!isObject(channel))
35
+ continue;
36
+ const servers = channel["servers"];
37
+ if (!Array.isArray(servers))
38
+ continue;
39
+ servers.forEach((entry, index) => {
40
+ const read = readEntry(entry);
41
+ if (read === void 0)
42
+ return;
43
+ const at = () => [...root.path, address, "servers", index, ...read.path];
44
+ if (read.kind === "elsewhere") {
45
+ if (!root.mustPointAtRootServers)
46
+ return;
47
+ results.push({
48
+ message: `A root channel's server must reference "${SERVERS_REF}\u2026", not "${read.ref}"`,
49
+ path: at()
50
+ });
51
+ } else if (!declared.has(read.name)) {
52
+ results.push({ message: `Channel server "${read.name}" is not defined in the "servers" object`, path: at() });
53
+ }
54
+ });
55
+ }
56
+ }
57
+ return results;
58
+ };
59
+ export {
60
+ asyncApiChannelServers
61
+ };
@@ -0,0 +1,17 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /**
3
+ * Validates a whole AsyncAPI document against the official structural
4
+ * meta-schema for the version it declares, parsing that version's schema on
5
+ * first use. A document declaring a version this package bundles no schema for
6
+ * (a future 2.7, say) reports nothing rather than being judged against a
7
+ * neighbouring version's schema.
8
+ *
9
+ * The rule that calls this runs `resolved: false`, against the document as
10
+ * written, which is what keeps one authored mistake to one finding. Validating
11
+ * the dereferenced tree instead would re-check every `components` entry once per
12
+ * `$ref` that reaches it, so a single bad reusable message reported three times
13
+ * in a document that used it twice. The cost is that content pulled in from
14
+ * another file is not structurally checked — the same trade-off the OpenAPI
15
+ * preset's `oas*-schema` rules make.
16
+ */
17
+ export declare const asyncApiDocumentSchema: RulesetFunction;
@@ -0,0 +1,12 @@
1
+ import { schema as schemaFunction } from "../../../functions/index.js";
2
+ import { asyncApiSchemaVersion, loadAsyncApiSchema } from "../schemas/index.js";
3
+ import { isObject } from "./helpers.js";
4
+ const asyncApiDocumentSchema = (input, _options, context) => {
5
+ const version = asyncApiSchemaVersion(isObject(input) ? input["asyncapi"] : void 0);
6
+ if (version === void 0)
7
+ return [];
8
+ return schemaFunction(input, { schema: loadAsyncApiSchema(version), allErrors: true }, context) ?? [];
9
+ };
10
+ export {
11
+ asyncApiDocumentSchema
12
+ };
@@ -0,0 +1,23 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /**
3
+ * A message's `headers` must describe an object — headers are name/value pairs,
4
+ * so any other type is a mistake.
5
+ *
6
+ * 3.0 lets `headers` be either a Schema Object or a Multi Format Schema Object
7
+ * (`{ schemaFormat, schema }`), and a plain `type: object` check rejected the
8
+ * second form outright — flagging documents the bundled meta-schema accepts, so
9
+ * the ruleset contradicted its own structural rule. When the headers are wrapped
10
+ * that way the check moves inside `schema`, and headers written in some other
11
+ * schema language are left alone, exactly as a non-AsyncAPI payload is.
12
+ */
13
+ /** Options for {@link asyncApiHeadersObject}. */
14
+ export type IAsyncApiHeadersOptions = {
15
+ /**
16
+ * Whether `headers` may be a Multi Format Schema Object (`{ schemaFormat,
17
+ * schema }`). That shape is 3.0 only — in 2.x `headers` is a Schema Object and
18
+ * nothing else, so accepting the wrapper there let a `schema` key switch the
19
+ * check off entirely on a document that never had one.
20
+ */
21
+ multiFormat?: boolean;
22
+ };
23
+ export declare const asyncApiHeadersObject: RulesetFunction<unknown, IAsyncApiHeadersOptions | undefined>;
@@ -0,0 +1,25 @@
1
+ import { isObject } from "./helpers.js";
2
+ import { isAsyncApiSchemaFormat } from "./schema-format.js";
3
+ const MESSAGE = 'Headers schema type must be "object"';
4
+ const asyncApiHeadersObject = (headers, options, context) => {
5
+ if (typeof headers === "boolean")
6
+ return [{ message: MESSAGE, path: [...context.path] }];
7
+ if (!isObject(headers))
8
+ return [];
9
+ if (typeof headers["$ref"] === "string")
10
+ return [];
11
+ if (options?.multiFormat === true && Object.hasOwn(headers, "schema")) {
12
+ if (!isAsyncApiSchemaFormat(headers["schemaFormat"]))
13
+ return [];
14
+ const inner = headers["schema"];
15
+ if (!isObject(inner))
16
+ return [{ message: MESSAGE, path: [...context.path, "schema"] }];
17
+ if (typeof inner["$ref"] === "string" || inner["type"] === "object")
18
+ return [];
19
+ return [{ message: MESSAGE, path: [...context.path, "schema"] }];
20
+ }
21
+ return headers["type"] === "object" ? [] : [{ message: MESSAGE, path: [...context.path] }];
22
+ };
23
+ export {
24
+ asyncApiHeadersObject
25
+ };
@@ -0,0 +1,8 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /**
3
+ * Checks every entry of a Message Object's `examples` against the message's own
4
+ * `payload` and `headers` schemas. Traits are folded in first, so an example is
5
+ * judged against the message a tool would actually assemble rather than against
6
+ * the half of it written inline.
7
+ */
8
+ export declare const asyncApiMessageExamples: RulesetFunction;
@@ -0,0 +1,42 @@
1
+ import { schema as schemaFunction } from "../../../functions/index.js";
2
+ import { isObject, mergeTraits } from "./helpers.js";
3
+ import { isAsyncApiSchemaFormat } from "./schema-format.js";
4
+ const examplesOrigin = (message) => {
5
+ const traits = message["traits"];
6
+ if (Array.isArray(traits)) {
7
+ for (let index = traits.length - 1; index >= 0; index--) {
8
+ const trait = traits[index];
9
+ if (isObject(trait) && Array.isArray(trait["examples"]))
10
+ return ["traits", index, "examples"];
11
+ }
12
+ }
13
+ return ["examples"];
14
+ };
15
+ const asyncApiMessageExamples = (input, _options, context) => {
16
+ if (!isObject(input))
17
+ return [];
18
+ const message = mergeTraits(input);
19
+ const examples = message["examples"];
20
+ if (!Array.isArray(examples))
21
+ return [];
22
+ const origin = examplesOrigin(input);
23
+ const payloadIsSchema = isAsyncApiSchemaFormat(message["schemaFormat"]);
24
+ const results = [];
25
+ examples.forEach((example, index) => {
26
+ if (!isObject(example))
27
+ return;
28
+ for (const part of ["payload", "headers"]) {
29
+ if (example[part] === void 0)
30
+ continue;
31
+ if (part === "payload" && !payloadIsSchema)
32
+ continue;
33
+ const findings = schemaFunction(example[part], { schema: isObject(message[part]) ? message[part] : {}, allErrors: true, skipUnusableSchema: true }, { ...context, path: [...context.path, ...origin, index, part] });
34
+ if (findings)
35
+ results.push(...findings);
36
+ }
37
+ });
38
+ return results;
39
+ };
40
+ export {
41
+ asyncApiMessageExamples
42
+ };
@@ -0,0 +1,3 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /** Ensures `messageId` values are unique across every message in a 2.x document. */
3
+ export declare const asyncApiMessageIdUnique: RulesetFunction;
@@ -0,0 +1,30 @@
1
+ import { getAllMessages, isObject } from "./helpers.js";
2
+ const effectiveMessageId = (message) => {
3
+ const traits = message["traits"];
4
+ if (Array.isArray(traits)) {
5
+ for (let index = traits.length - 1; index >= 0; index--) {
6
+ const trait = traits[index];
7
+ if (isObject(trait) && typeof trait["messageId"] === "string") {
8
+ return { id: trait["messageId"], path: ["traits", index, "messageId"] };
9
+ }
10
+ }
11
+ }
12
+ return typeof message["messageId"] === "string" ? { id: message["messageId"], path: ["messageId"] } : void 0;
13
+ };
14
+ const asyncApiMessageIdUnique = (document, _options, _context) => {
15
+ const seen = /* @__PURE__ */ new Set();
16
+ const results = [];
17
+ for (const { path, message } of getAllMessages(document)) {
18
+ const found = effectiveMessageId(message);
19
+ if (found === void 0)
20
+ continue;
21
+ if (seen.has(found.id)) {
22
+ results.push({ message: `messageId "${found.id}" must be unique`, path: [...path, ...found.path] });
23
+ }
24
+ seen.add(found.id);
25
+ }
26
+ return results;
27
+ };
28
+ export {
29
+ asyncApiMessageIdUnique
30
+ };
@@ -0,0 +1,3 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /** Ensures `operationId` values are unique across every operation in a 2.x document. */
3
+ export declare const asyncApiOperationIdUnique: RulesetFunction;
@@ -0,0 +1,30 @@
1
+ import { getAllOperations, isObject } from "./helpers.js";
2
+ const effectiveOperationId = (operation) => {
3
+ const traits = operation["traits"];
4
+ if (Array.isArray(traits)) {
5
+ for (let index = traits.length - 1; index >= 0; index--) {
6
+ const trait = traits[index];
7
+ if (isObject(trait) && typeof trait["operationId"] === "string") {
8
+ return { id: trait["operationId"], path: ["traits", index, "operationId"] };
9
+ }
10
+ }
11
+ }
12
+ return typeof operation["operationId"] === "string" ? { id: operation["operationId"], path: ["operationId"] } : void 0;
13
+ };
14
+ const asyncApiOperationIdUnique = (document, _options, _context) => {
15
+ const seen = /* @__PURE__ */ new Set();
16
+ const results = [];
17
+ for (const { path, operation } of getAllOperations(document)) {
18
+ const found = effectiveOperationId(operation);
19
+ if (found === void 0)
20
+ continue;
21
+ if (seen.has(found.id)) {
22
+ results.push({ message: `operationId "${found.id}" must be unique`, path: [...path, ...found.path] });
23
+ }
24
+ seen.add(found.id);
25
+ }
26
+ return results;
27
+ };
28
+ export {
29
+ asyncApiOperationIdUnique
30
+ };
@@ -0,0 +1,14 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /**
3
+ * Validates a message's `payload` against the AsyncAPI Schema Object definition
4
+ * of the document's own version — the check that catches a payload using a JSON
5
+ * Schema keyword AsyncAPI does not allow, or a `type` that is not a type.
6
+ *
7
+ * Takes the whole message rather than the payload so that traits are folded in
8
+ * before the `schemaFormat` is read. A payload in Avro or Protobuf is not an
9
+ * AsyncAPI Schema Object at all, and a `schemaFormat` naming one of those can be
10
+ * contributed by a trait — invisible to a `given` filter, which is how an Avro
11
+ * payload came to be judged as JSON Schema and reported at error severity.
12
+ * `asyncapi-payload-unsupported-schemaFormat` reports those separately.
13
+ */
14
+ export declare const asyncApiPayload: RulesetFunction;
@@ -0,0 +1,32 @@
1
+ import { schema as schemaFunction } from "../../../functions/index.js";
2
+ import { asyncApiSchemaVersion, loadAsyncApiSchema } from "../schemas/index.js";
3
+ import { isObject, mergeTraits } from "./helpers.js";
4
+ import { isAsyncApiSchemaFormat } from "./schema-format.js";
5
+ const payloadSchemaId = (version) => `http://asyncapi.com/definitions/${version}.0/schema.json`;
6
+ const payloadSchemas = /* @__PURE__ */ new Map();
7
+ const payloadSchema = (version) => {
8
+ let wrapper = payloadSchemas.get(version);
9
+ if (!wrapper) {
10
+ const meta = loadAsyncApiSchema(version);
11
+ wrapper = { $ref: payloadSchemaId(version), definitions: meta.definitions };
12
+ payloadSchemas.set(version, wrapper);
13
+ }
14
+ return wrapper;
15
+ };
16
+ const asyncApiPayload = (message, _options, context) => {
17
+ if (!isObject(message))
18
+ return [];
19
+ const merged = mergeTraits(message);
20
+ if (!isAsyncApiSchemaFormat(merged["schemaFormat"]))
21
+ return [];
22
+ const payload = merged["payload"];
23
+ if (payload === void 0)
24
+ return [];
25
+ const version = asyncApiSchemaVersion(isObject(context.document.data) ? context.document.data["asyncapi"] : void 0);
26
+ if (version === void 0)
27
+ return [];
28
+ return schemaFunction(payload, { schema: payloadSchema(version), allErrors: true }, { ...context, path: [...context.path, "payload"] }) ?? [];
29
+ };
30
+ export {
31
+ asyncApiPayload
32
+ };
@@ -0,0 +1,11 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /** Options for {@link asyncApiSchemaValidation}: which sibling of the schema to check. */
3
+ export type IAsyncApiSchemaValidationOptions = {
4
+ type: 'default' | 'examples';
5
+ };
6
+ /**
7
+ * Validates a Schema Object's own `default` or `examples` against that same
8
+ * schema. The rule targets the schema (via a `^` parent selector), so the input
9
+ * here is the schema and the values under test sit inside it.
10
+ */
11
+ export declare const asyncApiSchemaValidation: RulesetFunction<unknown, IAsyncApiSchemaValidationOptions>;
@@ -0,0 +1,17 @@
1
+ import { schema as schemaFunction } from "../../../functions/index.js";
2
+ import { isObject } from "./helpers.js";
3
+ const asyncApiSchemaValidation = (input, options, context) => {
4
+ if (!isObject(input) || options?.type === void 0)
5
+ return [];
6
+ const targets = options.type === "default" ? [{ path: ["default"], value: input["default"] }] : Array.isArray(input["examples"]) ? input["examples"].map((value, index) => ({ path: ["examples", index], value })) : [];
7
+ const results = [];
8
+ for (const target of targets) {
9
+ const findings = schemaFunction(target.value, { schema: input, allErrors: true, skipUnusableSchema: true }, { ...context, path: [...context.path, ...target.path] });
10
+ if (findings)
11
+ results.push(...findings);
12
+ }
13
+ return results;
14
+ };
15
+ export {
16
+ asyncApiSchemaValidation
17
+ };
@@ -0,0 +1,22 @@
1
+ import type { RulesetFunction } from '../../../core/types.js';
2
+ /** Options for {@link asyncApiSecurity}: which object the requirement hangs off, for the message. */
3
+ export type IAsyncApiSecurityOptions = {
4
+ objectType: 'Server' | 'Operation';
5
+ };
6
+ /**
7
+ * Checks that a security entry points at a security scheme the document
8
+ * declares. The two spec majors spell that entry differently, so the shape
9
+ * decides which check runs:
10
+ *
11
+ * - **2.x** — a Security Requirement Object: `{ <schemeName>: [<scope>, …] }`.
12
+ * Every key must name a declared scheme, and for an OAuth2 scheme every
13
+ * requested scope must be one its flows declare.
14
+ * - **3.x** — a Security Scheme Object or a Reference to one. A reference must
15
+ * point into `#/components/securitySchemes/` and name a scheme that exists; an
16
+ * inline scheme carries its own definition and has nothing to cross-check
17
+ * (the structural rules validate its shape).
18
+ *
19
+ * The lookup goes through the raw document, since a requirement names its scheme
20
+ * by key rather than by holding it.
21
+ */
22
+ export declare const asyncApiSecurity: RulesetFunction<unknown, IAsyncApiSecurityOptions>;
@@ -0,0 +1,78 @@
1
+ import { isObject } from "./helpers.js";
2
+ import { pointerSegment } from "./pointer.js";
3
+ const OAUTH2_FLOWS = ["implicit", "password", "clientCredentials", "authorizationCode"];
4
+ const SECURITY_SCHEMES_REF = "#/components/securitySchemes/";
5
+ const declaredScopes = (flows) => {
6
+ const scopes = /* @__PURE__ */ new Set();
7
+ if (!isObject(flows))
8
+ return scopes;
9
+ for (const name of OAUTH2_FLOWS) {
10
+ const flow = flows[name];
11
+ if (!isObject(flow) || !isObject(flow["scopes"]))
12
+ continue;
13
+ for (const scope of Object.keys(flow["scopes"]))
14
+ scopes.add(scope);
15
+ }
16
+ return scopes;
17
+ };
18
+ const securitySchemes = (data) => {
19
+ const components = isObject(data) ? data["components"] : void 0;
20
+ const schemes = isObject(components) ? components["securitySchemes"] : void 0;
21
+ return isObject(schemes) ? schemes : {};
22
+ };
23
+ const isDeclared = (schemes, name) => Object.hasOwn(schemes, name);
24
+ const asyncApiSecurity = (entry, options, context) => {
25
+ if (!isObject(entry))
26
+ return [];
27
+ const objectType = options?.objectType ?? "Operation";
28
+ const schemes = securitySchemes(context.document.data);
29
+ const ref = entry["$ref"];
30
+ if (typeof ref === "string") {
31
+ if (!ref.startsWith(SECURITY_SCHEMES_REF)) {
32
+ return [
33
+ {
34
+ message: `${objectType} security must reference "${SECURITY_SCHEMES_REF}\u2026", not "${ref}"`,
35
+ path: [...context.path, "$ref"]
36
+ }
37
+ ];
38
+ }
39
+ const name = pointerSegment(ref.slice(SECURITY_SCHEMES_REF.length));
40
+ return isDeclared(schemes, name) ? [] : [
41
+ {
42
+ message: `${objectType} security requirement "${name}" is not a defined security scheme`,
43
+ path: [...context.path, "$ref"]
44
+ }
45
+ ];
46
+ }
47
+ if (typeof entry["type"] === "string")
48
+ return [];
49
+ const results = [];
50
+ for (const key of Object.keys(entry)) {
51
+ const requested = entry[key];
52
+ if (!Array.isArray(requested))
53
+ continue;
54
+ if (!isDeclared(schemes, key)) {
55
+ results.push({
56
+ message: `${objectType} security requirement "${key}" is not a defined security scheme`,
57
+ path: [...context.path, key]
58
+ });
59
+ continue;
60
+ }
61
+ const scheme = schemes[key];
62
+ if (!isObject(scheme) || scheme["type"] !== "oauth2")
63
+ continue;
64
+ const available = declaredScopes(scheme["flows"]);
65
+ requested.forEach((scope, index) => {
66
+ if (typeof scope === "string" && !available.has(scope)) {
67
+ results.push({
68
+ message: `Security scope "${scope}" is not declared by the "${key}" scheme. Available: [${[...available].join(", ")}]`,
69
+ path: [...context.path, key, index]
70
+ });
71
+ }
72
+ });
73
+ }
74
+ return results;
75
+ };
76
+ export {
77
+ asyncApiSecurity
78
+ };
@@ -0,0 +1,51 @@
1
+ import type { JsonPath } from '../../../core/types.js';
2
+ export { isObject } from '../../shared/helpers.js';
3
+ /** The two operation fields an AsyncAPI 2.x Channel Item Object may carry. */
4
+ export declare const V2_OPERATIONS: readonly ['subscribe', 'publish'];
5
+ /** One AsyncAPI 2.x operation, with the path it sits at. */
6
+ export type LocatedOperation = {
7
+ path: JsonPath;
8
+ kind: (typeof V2_OPERATIONS)[number];
9
+ operation: Record<string, unknown>;
10
+ };
11
+ /** One AsyncAPI 2.x message, with the path it sits at. */
12
+ export type LocatedMessage = {
13
+ path: JsonPath;
14
+ message: Record<string, unknown>;
15
+ };
16
+ /**
17
+ * Every `{variable}` name in a channel address or server URL, in order. A name
18
+ * is a run of non-`}` characters, so `{}` yields nothing (the dedicated
19
+ * `*-no-empty-parameter` rules report that).
20
+ *
21
+ * The character class matters: a lazy `.+?` swallowed the closing brace of `{}`
22
+ * and ran on to the next one, so `a/{}/b/{id}` yielded the single name
23
+ * `}/b/{id` — a parameter nobody wrote, while the one that really was
24
+ * undescribed went unnamed.
25
+ */
26
+ export declare const parseUrlVariables: (value: unknown) => string[];
27
+ /**
28
+ * Walks the `publish`/`subscribe` operations of every 2.x channel — both the
29
+ * channels the document serves and the reusable ones under
30
+ * `components.channels`. A reusable channel is a declaration like any other, so
31
+ * a rule asking for "every operation" has to see it; leaving it out meant a
32
+ * duplicate `operationId` between a served channel and a reusable one went
33
+ * unreported.
34
+ */
35
+ export declare function getAllOperations(document: unknown): Generator<LocatedOperation>;
36
+ /**
37
+ * Walks every 2.x message: each operation's `message` (or, when that message is a
38
+ * `oneOf` list, each alternative in it), plus every reusable message under
39
+ * `components.messages`. These are the same locations the ruleset's `V2_MESSAGES`
40
+ * enumerates, so "every message" means the same thing to a rule and to this
41
+ * walker.
42
+ */
43
+ export declare function getAllMessages(document: unknown): Generator<LocatedMessage>;
44
+ /**
45
+ * Folds a Message (or Operation) Object's `traits` into the object itself, the
46
+ * way a 2.x tool resolving the document would. The spec applies each trait as a
47
+ * JSON Merge Patch in declaration order, so a trait overrides what the object
48
+ * (and any earlier trait) declared. Returns the input unchanged when it declares
49
+ * no `traits`.
50
+ */
51
+ export declare const mergeTraits: (target: Record<string, unknown>) => Record<string, unknown>;