@amritk/lint 0.1.0 → 0.3.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 (108) hide show
  1. package/README.md +52 -1
  2. package/dist/core/glob.d.ts +1 -1
  3. package/dist/core/glob.js +89 -5
  4. package/dist/core/index.d.ts +1 -1
  5. package/dist/core/index.js +1 -1
  6. package/dist/core/jsonpath.d.ts +17 -1
  7. package/dist/core/jsonpath.js +218 -23
  8. package/dist/core/lint.d.ts +15 -8
  9. package/dist/core/lint.js +12 -3
  10. package/dist/core/plugin.d.ts +6 -0
  11. package/dist/core/plugin.js +6 -0
  12. package/dist/core/pointers.js +15 -15
  13. package/dist/core/ruleset.js +0 -0
  14. package/dist/core/runner.d.ts +6 -1
  15. package/dist/core/runner.js +127 -43
  16. package/dist/core/types.d.ts +17 -2
  17. package/dist/core/validate-ruleset.js +16 -0
  18. package/dist/fix/apply.d.ts +8 -2
  19. package/dist/fix/apply.js +68 -18
  20. package/dist/functions/alphabetical.js +40 -13
  21. package/dist/functions/casing.js +27 -5
  22. package/dist/functions/enumeration.d.ts +5 -3
  23. package/dist/functions/enumeration.js +18 -1
  24. package/dist/functions/index.d.ts +1 -0
  25. package/dist/functions/index.js +3 -0
  26. package/dist/functions/length.d.ts +13 -3
  27. package/dist/functions/length.js +11 -3
  28. package/dist/functions/or.d.ts +11 -0
  29. package/dist/functions/or.js +25 -0
  30. package/dist/functions/pattern.d.ts +5 -3
  31. package/dist/functions/pattern.js +42 -9
  32. package/dist/functions/schema.d.ts +13 -0
  33. package/dist/functions/schema.js +95 -2
  34. package/dist/functions/typed-enum.js +7 -1
  35. package/dist/functions/unreferenced-reusable-object.d.ts +7 -1
  36. package/dist/functions/unreferenced-reusable-object.js +18 -3
  37. package/dist/functions/xor.js +8 -1
  38. package/dist/index.js +9 -1
  39. package/dist/parsers/edit-model.d.ts +15 -0
  40. package/dist/parsers/edit-model.js +210 -41
  41. package/dist/parsers/types.d.ts +14 -2
  42. package/dist/parsers/yaml.d.ts +10 -0
  43. package/dist/parsers/yaml.js +174 -26
  44. package/dist/rules/openapi/fixers.d.ts +7 -0
  45. package/dist/rules/openapi/fixers.js +248 -0
  46. package/dist/rules/openapi/formats.d.ts +13 -0
  47. package/dist/rules/openapi/formats.js +30 -0
  48. package/dist/rules/openapi/functions/example-validation.d.ts +18 -0
  49. package/dist/rules/openapi/functions/example-validation.js +160 -0
  50. package/dist/rules/openapi/functions/helpers.d.ts +4 -0
  51. package/dist/rules/openapi/functions/helpers.js +10 -0
  52. package/dist/rules/openapi/functions/index.d.ts +26 -0
  53. package/dist/rules/openapi/functions/index.js +73 -0
  54. package/dist/rules/openapi/functions/oas-additional-operations.d.ts +8 -0
  55. package/dist/rules/openapi/functions/oas-additional-operations.js +25 -0
  56. package/dist/rules/openapi/functions/oas-discriminator.d.ts +3 -0
  57. package/dist/rules/openapi/functions/oas-discriminator.js +23 -0
  58. package/dist/rules/openapi/functions/oas-example-external-value.d.ts +11 -0
  59. package/dist/rules/openapi/functions/oas-example-external-value.js +23 -0
  60. package/dist/rules/openapi/functions/oas-example-value.d.ts +3 -0
  61. package/dist/rules/openapi/functions/oas-example-value.js +31 -0
  62. package/dist/rules/openapi/functions/oas-mutually-exclusive.d.ts +10 -0
  63. package/dist/rules/openapi/functions/oas-mutually-exclusive.js +20 -0
  64. package/dist/rules/openapi/functions/oas-no-nullable.d.ts +13 -0
  65. package/dist/rules/openapi/functions/oas-no-nullable.js +22 -0
  66. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.d.ts +3 -0
  67. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -0
  68. package/dist/rules/openapi/functions/oas-op-id-unique.d.ts +3 -0
  69. package/dist/rules/openapi/functions/oas-op-id-unique.js +29 -0
  70. package/dist/rules/openapi/functions/oas-op-params.d.ts +9 -0
  71. package/dist/rules/openapi/functions/oas-op-params.js +46 -0
  72. package/dist/rules/openapi/functions/oas-op-security-defined.d.ts +5 -0
  73. package/dist/rules/openapi/functions/oas-op-security-defined.js +44 -0
  74. package/dist/rules/openapi/functions/oas-op-success-response.d.ts +3 -0
  75. package/dist/rules/openapi/functions/oas-op-success-response.js +16 -0
  76. package/dist/rules/openapi/functions/oas-path-param.d.ts +12 -0
  77. package/dist/rules/openapi/functions/oas-path-param.js +106 -0
  78. package/dist/rules/openapi/functions/oas-schema-example-deprecated.d.ts +10 -0
  79. package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +42 -0
  80. package/dist/rules/openapi/functions/oas-schema.d.ts +15 -0
  81. package/dist/rules/openapi/functions/oas-schema.js +15 -0
  82. package/dist/rules/openapi/functions/oas-server-name-unique.d.ts +3 -0
  83. package/dist/rules/openapi/functions/oas-server-name-unique.js +20 -0
  84. package/dist/rules/openapi/functions/oas-server-variables.d.ts +8 -0
  85. package/dist/rules/openapi/functions/oas-server-variables.js +52 -0
  86. package/dist/rules/openapi/functions/oas-tag-defined.d.ts +3 -0
  87. package/dist/rules/openapi/functions/oas-tag-defined.js +21 -0
  88. package/dist/rules/openapi/functions/oas-tag-kind.d.ts +3 -0
  89. package/dist/rules/openapi/functions/oas-tag-kind.js +17 -0
  90. package/dist/rules/openapi/functions/oas-tag-parent-defined.d.ts +7 -0
  91. package/dist/rules/openapi/functions/oas-tag-parent-defined.js +45 -0
  92. package/dist/rules/openapi/functions/oas-tags-unique.d.ts +3 -0
  93. package/dist/rules/openapi/functions/oas-tags-unique.js +17 -0
  94. package/dist/rules/openapi/functions/oas-unused-component.d.ts +7 -0
  95. package/dist/rules/openapi/functions/oas-unused-component.js +65 -0
  96. package/dist/rules/openapi/functions/ref-siblings.d.ts +3 -0
  97. package/dist/rules/openapi/functions/ref-siblings.js +13 -0
  98. package/dist/rules/openapi/index.d.ts +25 -0
  99. package/dist/rules/openapi/index.js +127 -0
  100. package/dist/rules/openapi/oas.d.ts +3 -0
  101. package/dist/rules/openapi/oas.js +549 -0
  102. package/dist/rules/openapi/schemas/index.d.ts +8 -0
  103. package/dist/rules/openapi/schemas/index.js +36 -0
  104. package/dist/rules/openapi/schemas/oas20.json +1592 -0
  105. package/dist/rules/openapi/schemas/oas30.json +1651 -0
  106. package/dist/rules/openapi/schemas/oas31.json +1412 -0
  107. package/dist/rules/openapi/schemas/oas32.json +1684 -0
  108. package/package.json +17 -5
@@ -0,0 +1,160 @@
1
+ import { validate } from '@amritk/runtime-validators';
2
+ import { isObject } from './helpers.js';
3
+ // The example schemas come from the *linted document*, so they are only known at
4
+ // runtime. `@amritk/runtime-validators` interprets a schema directly — no
5
+ // `ajv.compile` (whose codegen dominated lint time on large specs, recompiling
6
+ // `$ref`-duplicated schemas tens of thousands of times) and no `new Function`,
7
+ // so it is also CSP/edge-runtime safe. Returns undefined for a non-object or a
8
+ // schema the validator can't build (mirrors the old skip-on-compile-failure
9
+ // behavior).
10
+ //
11
+ // Formats are asserted (`formats: 'all'`) to match Spectral, whose example rules
12
+ // run ajv with `ajv-formats` enabled — otherwise a format-violating example (a
13
+ // bad `email`/`date`/`uuid`) would slip through. This mirrors the core `schema`
14
+ // built-in's option exactly; `@amritk/runtime-validators` treats the OAS-specific
15
+ // numeric/binary formats (`int32`/`int64`/`float`/`byte`/`binary`) as non-failing.
16
+ const buildValidator = (schema) => {
17
+ if (!isObject(schema))
18
+ return undefined;
19
+ let validator;
20
+ try {
21
+ validator = validate(schema, { formats: 'all' });
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ // A schema can carry a `$ref` the runtime validator cannot resolve (an external
27
+ // or cyclic ref left behind after `$ref` inlining), which throws at *run* time
28
+ // rather than build time. Treat that as "cannot validate — skip" (returning a
29
+ // valid result) so an unresolvable example schema never crashes the whole lint.
30
+ return (input) => {
31
+ try {
32
+ return validator(input);
33
+ }
34
+ catch {
35
+ return true;
36
+ }
37
+ };
38
+ };
39
+ const buildValidatorOrNull = (schema) => buildValidator(schema) ?? null;
40
+ // Keyed by the Schema Object node; `oasSchemaExample` validates a schema's own
41
+ // `example`/`default` against the schema minus its `example`/`examples` keywords.
42
+ const schemaExampleResults = new WeakMap();
43
+ // Keyed by the Media Type / Response Object node; `oasMediaExample` validates the
44
+ // object's example(s) against its `schema`. The node (not the schema) is the cache
45
+ // key because two media objects can share a `schema` but carry different examples.
46
+ const mediaExampleResults = new WeakMap();
47
+ const withPath = (findings, path) => {
48
+ if (findings.length === 0)
49
+ return [];
50
+ return findings.map((finding) => ({ message: finding.message, path: [...path, ...finding.suffix] }));
51
+ };
52
+ // A schema node reached via `$..[?(...)]` might be a `properties`/`patternProperties`
53
+ // *map* whose keys happen to look like schema keywords (e.g. a property literally
54
+ // named `type`). Those maps are not Schema Objects, so we never validate them.
55
+ const isPropertiesMap = (path) => {
56
+ const tail = path[path.length - 1];
57
+ return tail === 'properties' || tail === 'patternProperties';
58
+ };
59
+ /** Validates a schema object's inline `example` and `default` against the schema itself. */
60
+ export const oasSchemaExample = (schema, _options, context) => {
61
+ if (!isObject(schema))
62
+ return [];
63
+ if (schema['example'] === undefined && schema['default'] === undefined)
64
+ return [];
65
+ if (isPropertiesMap(context.path))
66
+ return [];
67
+ let findings = schemaExampleResults.get(schema);
68
+ if (findings === undefined) {
69
+ findings = [];
70
+ // `example`/`examples` are annotations, not constraints, so drop them before
71
+ // building the validator; `default` stays (it is not a validation keyword).
72
+ const { example, examples, ...rest } = schema;
73
+ void example;
74
+ void examples;
75
+ const check = buildValidatorOrNull(rest);
76
+ if (check) {
77
+ for (const field of ['example', 'default']) {
78
+ if (schema[field] === undefined)
79
+ continue;
80
+ const result = check(schema[field]);
81
+ if (result !== true) {
82
+ for (const error of result.errors)
83
+ findings.push({ message: `"${field}" ${error.message}`.trim(), suffix: [field] });
84
+ }
85
+ }
86
+ }
87
+ schemaExampleResults.set(schema, findings);
88
+ }
89
+ return withPath(findings, context.path);
90
+ };
91
+ /**
92
+ * Validates a Media Type / Response / Parameter object's examples against its
93
+ * `schema`. Version-split because OpenAPI 2.0 and 3.x model examples differently:
94
+ * - OAS3: a singular `example` value plus an `examples` map of Example Objects,
95
+ * each of which carries the value under `value`.
96
+ * - OAS2: `examples` is a MIME-type → value map (`{ 'application/json': value }`),
97
+ * with no Example Objects and no singular `example` on the media object — so the
98
+ * 3.x logic validated nothing at all for a real 2.0 document.
99
+ */
100
+ export const oasMediaExample = (media, options, context) => {
101
+ if (!isObject(media) || !isObject(media['schema']))
102
+ return [];
103
+ const oasVersion = options?.oasVersion ?? 3;
104
+ // Skip building a validator when there is nothing to check — most media objects
105
+ // in a large spec have a schema but no example. (OAS2 only has the map form.)
106
+ const hasExample = (oasVersion !== 2 && media['example'] !== undefined) || isObject(media['examples']);
107
+ if (!hasExample)
108
+ return [];
109
+ let findings = mediaExampleResults.get(media);
110
+ if (findings === undefined) {
111
+ findings = [];
112
+ const check = buildValidatorOrNull(media['schema']);
113
+ if (check) {
114
+ if (oasVersion === 2)
115
+ collectOas2(media, check, findings);
116
+ else
117
+ collectOas3(media, check, findings);
118
+ }
119
+ mediaExampleResults.set(media, findings);
120
+ }
121
+ return withPath(findings, context.path);
122
+ };
123
+ /** OAS2: validate each `examples[mimeType]` value against the sibling `schema`. */
124
+ const collectOas2 = (media, check, findings) => {
125
+ const examples = isObject(media['examples']) ? media['examples'] : undefined;
126
+ if (!examples)
127
+ return;
128
+ for (const [mimeType, value] of Object.entries(examples)) {
129
+ const result = check(value);
130
+ if (result !== true) {
131
+ for (const error of result.errors) {
132
+ findings.push({ message: `Example "${mimeType}" ${error.message}`.trim(), suffix: ['examples', mimeType] });
133
+ }
134
+ }
135
+ }
136
+ };
137
+ /** OAS3: validate the singular `example` and each `examples[name].value` against the `schema`. */
138
+ const collectOas3 = (media, check, findings) => {
139
+ if (media['example'] !== undefined) {
140
+ const result = check(media['example']);
141
+ if (result !== true) {
142
+ for (const error of result.errors)
143
+ findings.push({ message: `"example" ${error.message}`.trim(), suffix: ['example'] });
144
+ }
145
+ }
146
+ const examples = isObject(media['examples']) ? media['examples'] : undefined;
147
+ if (!examples)
148
+ return;
149
+ for (const [name, example] of Object.entries(examples)) {
150
+ // An external-value example is fetched elsewhere, so there is nothing inline to check.
151
+ if (isObject(example) && example['value'] !== undefined) {
152
+ const result = check(example['value']);
153
+ if (result !== true) {
154
+ for (const error of result.errors) {
155
+ findings.push({ message: `Example "${name}" ${error.message}`.trim(), suffix: ['examples', name, 'value'] });
156
+ }
157
+ }
158
+ }
159
+ }
160
+ };
@@ -0,0 +1,4 @@
1
+ /** True for a non-null, non-array object (an OpenAPI "object" value). */
2
+ export declare const isObject: (value: unknown) => value is Record<string, unknown>;
3
+ export declare const HTTP_METHODS: Set<string>;
4
+ export declare const OPERATION_METHODS: Set<string>;
@@ -0,0 +1,10 @@
1
+ /** True for a non-null, non-array object (an OpenAPI "object" value). */
2
+ export const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ // The eight standard HTTP methods that have a dedicated fixed field on the Path
4
+ // Item Object. Shared by the rules that iterate a path item's operations.
5
+ export const HTTP_METHODS = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']);
6
+ // OpenAPI 3.2 promoted `query` to a fixed Path Item Object operation field, so the
7
+ // rules that walk a path item's operations must consider it too. Including it is
8
+ // harmless on older versions, which never carry a `query` operation, so the same
9
+ // set works across every OpenAPI version.
10
+ export const OPERATION_METHODS = new Set([...HTTP_METHODS, 'query']);
@@ -0,0 +1,26 @@
1
+ import type { FunctionRegistry } from '../../../core/index.js';
2
+ export { type IOasExampleOptions, oasMediaExample, oasSchemaExample } from './example-validation.js';
3
+ export { oasAdditionalOperations } from './oas-additional-operations.js';
4
+ export { oasDiscriminator } from './oas-discriminator.js';
5
+ export { oasExampleExternalValue } from './oas-example-external-value.js';
6
+ export { oasExampleValue } from './oas-example-value.js';
7
+ export { oasMutuallyExclusive } from './oas-mutually-exclusive.js';
8
+ export { oasNoNullable } from './oas-no-nullable.js';
9
+ export { oasOpFormDataConsumeCheck } from './oas-op-form-data-consume-check.js';
10
+ export { oasOpIdUnique } from './oas-op-id-unique.js';
11
+ export { oasOpParams } from './oas-op-params.js';
12
+ export { oasOpSecurityDefined } from './oas-op-security-defined.js';
13
+ export { oasOpSuccessResponse } from './oas-op-success-response.js';
14
+ export { oasPathParam } from './oas-path-param.js';
15
+ export { type IOasSchemaOptions, oasSchema } from './oas-schema.js';
16
+ export { oasSchemaExampleDeprecated } from './oas-schema-example-deprecated.js';
17
+ export { oasServerNameUnique } from './oas-server-name-unique.js';
18
+ export { oasServerVariables } from './oas-server-variables.js';
19
+ export { oasTagDefined } from './oas-tag-defined.js';
20
+ export { oasTagKind } from './oas-tag-kind.js';
21
+ export { oasTagParentDefined } from './oas-tag-parent-defined.js';
22
+ export { oasTagsUnique } from './oas-tags-unique.js';
23
+ export { oasUnusedComponent } from './oas-unused-component.js';
24
+ export { refSiblings } from './ref-siblings.js';
25
+ /** The OpenAPI-specific custom functions, keyed by name for ruleset `then` references. */
26
+ export declare const oasFunctions: FunctionRegistry;
@@ -0,0 +1,73 @@
1
+ import { oasMediaExample, oasSchemaExample } from './example-validation.js';
2
+ import { oasAdditionalOperations } from './oas-additional-operations.js';
3
+ import { oasDiscriminator } from './oas-discriminator.js';
4
+ import { oasExampleExternalValue } from './oas-example-external-value.js';
5
+ import { oasExampleValue } from './oas-example-value.js';
6
+ import { oasMutuallyExclusive } from './oas-mutually-exclusive.js';
7
+ import { oasNoNullable } from './oas-no-nullable.js';
8
+ import { oasOpFormDataConsumeCheck } from './oas-op-form-data-consume-check.js';
9
+ import { oasOpIdUnique } from './oas-op-id-unique.js';
10
+ import { oasOpParams } from './oas-op-params.js';
11
+ import { oasOpSecurityDefined } from './oas-op-security-defined.js';
12
+ import { oasOpSuccessResponse } from './oas-op-success-response.js';
13
+ import { oasPathParam } from './oas-path-param.js';
14
+ import { oasSchema } from './oas-schema.js';
15
+ import { oasSchemaExampleDeprecated } from './oas-schema-example-deprecated.js';
16
+ import { oasServerNameUnique } from './oas-server-name-unique.js';
17
+ import { oasServerVariables } from './oas-server-variables.js';
18
+ import { oasTagDefined } from './oas-tag-defined.js';
19
+ import { oasTagKind } from './oas-tag-kind.js';
20
+ import { oasTagParentDefined } from './oas-tag-parent-defined.js';
21
+ import { oasTagsUnique } from './oas-tags-unique.js';
22
+ import { oasUnusedComponent } from './oas-unused-component.js';
23
+ import { refSiblings } from './ref-siblings.js';
24
+ export { oasMediaExample, oasSchemaExample } from './example-validation.js';
25
+ export { oasAdditionalOperations } from './oas-additional-operations.js';
26
+ export { oasDiscriminator } from './oas-discriminator.js';
27
+ export { oasExampleExternalValue } from './oas-example-external-value.js';
28
+ export { oasExampleValue } from './oas-example-value.js';
29
+ export { oasMutuallyExclusive } from './oas-mutually-exclusive.js';
30
+ export { oasNoNullable } from './oas-no-nullable.js';
31
+ export { oasOpFormDataConsumeCheck } from './oas-op-form-data-consume-check.js';
32
+ export { oasOpIdUnique } from './oas-op-id-unique.js';
33
+ export { oasOpParams } from './oas-op-params.js';
34
+ export { oasOpSecurityDefined } from './oas-op-security-defined.js';
35
+ export { oasOpSuccessResponse } from './oas-op-success-response.js';
36
+ export { oasPathParam } from './oas-path-param.js';
37
+ export { oasSchema } from './oas-schema.js';
38
+ export { oasSchemaExampleDeprecated } from './oas-schema-example-deprecated.js';
39
+ export { oasServerNameUnique } from './oas-server-name-unique.js';
40
+ export { oasServerVariables } from './oas-server-variables.js';
41
+ export { oasTagDefined } from './oas-tag-defined.js';
42
+ export { oasTagKind } from './oas-tag-kind.js';
43
+ export { oasTagParentDefined } from './oas-tag-parent-defined.js';
44
+ export { oasTagsUnique } from './oas-tags-unique.js';
45
+ export { oasUnusedComponent } from './oas-unused-component.js';
46
+ export { refSiblings } from './ref-siblings.js';
47
+ /** The OpenAPI-specific custom functions, keyed by name for ruleset `then` references. */
48
+ export const oasFunctions = {
49
+ refSiblings,
50
+ oasOpSuccessResponse,
51
+ oasTagDefined,
52
+ oasOpIdUnique,
53
+ oasPathParam,
54
+ oasOpParams,
55
+ oasTagsUnique,
56
+ oasOpSecurityDefined: oasOpSecurityDefined,
57
+ oasOpFormDataConsumeCheck,
58
+ oasDiscriminator,
59
+ oasServerVariables,
60
+ oasSchemaExample,
61
+ oasMediaExample: oasMediaExample,
62
+ oasUnusedComponent,
63
+ oasMutuallyExclusive: oasMutuallyExclusive,
64
+ oasNoNullable,
65
+ oasSchema: oasSchema,
66
+ oasAdditionalOperations,
67
+ oasServerNameUnique,
68
+ oasTagParentDefined,
69
+ oasSchemaExampleDeprecated,
70
+ oasTagKind,
71
+ oasExampleValue,
72
+ oasExampleExternalValue,
73
+ };
@@ -0,0 +1,8 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /**
3
+ * Flags standard HTTP methods inside an OpenAPI 3.2 `additionalOperations` map.
4
+ * The spec reserves that map for methods without a dedicated fixed field, so a
5
+ * standard method key (sent uppercase, e.g. `POST`) belongs in the lowercase
6
+ * fixed field (`post`) instead.
7
+ */
8
+ export declare const oasAdditionalOperations: RulesetFunction;
@@ -0,0 +1,25 @@
1
+ import { isObject, OPERATION_METHODS } from './helpers.js';
2
+ // Methods that have a dedicated fixed field on the Path Item Object (the eight
3
+ // standard methods plus 3.2's `query`). These MUST NOT be redefined inside the
4
+ // new `additionalOperations` map, which is reserved for non-standard methods.
5
+ const FIXED_PATH_ITEM_METHODS = OPERATION_METHODS;
6
+ /**
7
+ * Flags standard HTTP methods inside an OpenAPI 3.2 `additionalOperations` map.
8
+ * The spec reserves that map for methods without a dedicated fixed field, so a
9
+ * standard method key (sent uppercase, e.g. `POST`) belongs in the lowercase
10
+ * fixed field (`post`) instead.
11
+ */
12
+ export const oasAdditionalOperations = (input, _options, context) => {
13
+ if (!isObject(input))
14
+ return [];
15
+ const results = [];
16
+ for (const method of Object.keys(input)) {
17
+ if (FIXED_PATH_ITEM_METHODS.has(method.toLowerCase())) {
18
+ results.push({
19
+ message: `"additionalOperations" must not redefine the standard method "${method}"; use the "${method.toLowerCase()}" field instead`,
20
+ path: [...context.path, method],
21
+ });
22
+ }
23
+ }
24
+ return results;
25
+ };
@@ -0,0 +1,3 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /** Validates a v2 discriminator references a required property. */
3
+ export declare const oasDiscriminator: RulesetFunction;
@@ -0,0 +1,23 @@
1
+ import { isObject } from './helpers.js';
2
+ /** Validates a v2 discriminator references a required property. */
3
+ export const oasDiscriminator = (schema, _options, context) => {
4
+ if (!isObject(schema) || typeof schema['discriminator'] !== 'string')
5
+ return [];
6
+ const property = schema['discriminator'];
7
+ const required = Array.isArray(schema['required']) ? schema['required'] : [];
8
+ const properties = isObject(schema['properties']) ? schema['properties'] : {};
9
+ const results = [];
10
+ if (!(property in properties)) {
11
+ results.push({
12
+ message: `Discriminator "${property}" must be defined in properties`,
13
+ path: [...context.path, 'discriminator'],
14
+ });
15
+ }
16
+ if (!required.includes(property)) {
17
+ results.push({
18
+ message: `Discriminator "${property}" must be a required property`,
19
+ path: [...context.path, 'discriminator'],
20
+ });
21
+ }
22
+ return results;
23
+ };
@@ -0,0 +1,11 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /**
3
+ * Enforces that an Example Object carries exactly one inline value source. Across
4
+ * all of OpenAPI 3.x an Example must use either `value` or `externalValue` (not
5
+ * both, not neither). OpenAPI 3.2 added `dataValue`/`serializedValue` as further
6
+ * ways to supply the example, so their presence also satisfies the "has an
7
+ * example" requirement — otherwise a valid 3.2 `dataValue`-only example would be
8
+ * wrongly flagged. The mutual exclusivity of the new fields is policed separately
9
+ * by `oas3_2-example-value`.
10
+ */
11
+ export declare const oasExampleExternalValue: RulesetFunction;
@@ -0,0 +1,23 @@
1
+ import { isObject } from './helpers.js';
2
+ /**
3
+ * Enforces that an Example Object carries exactly one inline value source. Across
4
+ * all of OpenAPI 3.x an Example must use either `value` or `externalValue` (not
5
+ * both, not neither). OpenAPI 3.2 added `dataValue`/`serializedValue` as further
6
+ * ways to supply the example, so their presence also satisfies the "has an
7
+ * example" requirement — otherwise a valid 3.2 `dataValue`-only example would be
8
+ * wrongly flagged. The mutual exclusivity of the new fields is policed separately
9
+ * by `oas3_2-example-value`.
10
+ */
11
+ export const oasExampleExternalValue = (example) => {
12
+ if (!isObject(example))
13
+ return [];
14
+ // A 3.2 dataValue/serializedValue already provides the example, so value /
15
+ // externalValue are optional in that case.
16
+ if (example['dataValue'] !== undefined || example['serializedValue'] !== undefined)
17
+ return [];
18
+ const present = ['value', 'externalValue'].filter((property) => property in example);
19
+ if (present.length !== 1) {
20
+ return [{ message: 'Example object must have exactly one of "value" or "externalValue"' }];
21
+ }
22
+ return [];
23
+ };
@@ -0,0 +1,3 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /** Flags forbidden field combinations on an OpenAPI 3.2 Example Object. */
3
+ export declare const oasExampleValue: RulesetFunction;
@@ -0,0 +1,31 @@
1
+ import { isObject } from './helpers.js';
2
+ // OpenAPI 3.2 Example Object exclusivity (MUST-level, per the spec's field
3
+ // table). The `value`/`externalValue` pair is intentionally omitted here — the
4
+ // 3.x-wide `oas3-examples-value-or-externalValue` rule already covers it, so we
5
+ // only police the constraints introduced with the new `dataValue` /
6
+ // `serializedValue` fields to avoid double-reporting.
7
+ const EXAMPLE_EXCLUSIONS = [
8
+ // dataValue MUST NOT accompany value.
9
+ { field: 'dataValue', conflicts: ['value'] },
10
+ // serializedValue MUST NOT accompany value or externalValue.
11
+ { field: 'serializedValue', conflicts: ['value', 'externalValue'] },
12
+ ];
13
+ /** Flags forbidden field combinations on an OpenAPI 3.2 Example Object. */
14
+ export const oasExampleValue = (example, _options, context) => {
15
+ if (!isObject(example))
16
+ return [];
17
+ const results = [];
18
+ for (const { field, conflicts } of EXAMPLE_EXCLUSIONS) {
19
+ if (example[field] === undefined)
20
+ continue;
21
+ for (const other of conflicts) {
22
+ if (example[other] !== undefined) {
23
+ results.push({
24
+ message: `"${field}" must not be used together with "${other}"`,
25
+ path: [...context.path, other],
26
+ });
27
+ }
28
+ }
29
+ }
30
+ return results;
31
+ };
@@ -0,0 +1,10 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /**
3
+ * Flags objects that carry more than one of a set of mutually exclusive
4
+ * properties. OpenAPI 3.1's License Object, for example, defines `identifier`
5
+ * (SPDX) as "mutually exclusive of the url field" — neither, or exactly one, is
6
+ * allowed, but not both.
7
+ */
8
+ export declare const oasMutuallyExclusive: RulesetFunction<Record<string, unknown>, {
9
+ properties: string[];
10
+ }>;
@@ -0,0 +1,20 @@
1
+ import { isObject } from './helpers.js';
2
+ /**
3
+ * Flags objects that carry more than one of a set of mutually exclusive
4
+ * properties. OpenAPI 3.1's License Object, for example, defines `identifier`
5
+ * (SPDX) as "mutually exclusive of the url field" — neither, or exactly one, is
6
+ * allowed, but not both.
7
+ */
8
+ export const oasMutuallyExclusive = (input, options, context) => {
9
+ if (!isObject(input))
10
+ return [];
11
+ const present = (options?.properties ?? []).filter((property) => input[property] !== undefined);
12
+ if (present.length <= 1)
13
+ return [];
14
+ // The first present property is the "anchor"; flag every later one as the conflict.
15
+ const [anchor, ...conflicts] = present;
16
+ return conflicts.map((property) => ({
17
+ message: `"${property}" must not be used together with "${anchor}" (mutually exclusive)`,
18
+ path: [...context.path, property],
19
+ }));
20
+ };
@@ -0,0 +1,13 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /**
3
+ * Flags a Schema Object's `nullable` keyword, removed in OpenAPI 3.1+ (JSON
4
+ * Schema 2020-12 uses a `"null"` type instead). Targets the *parent* of a
5
+ * `nullable` key (`$..nullable^`) rather than `$..nullable` directly, which fixes
6
+ * two problems with the naive `then: falsy` approach:
7
+ * - a property literally named `nullable` (`properties: { nullable: {...} }`)
8
+ * is no longer flagged, because there the `nullable` value is a Schema Object,
9
+ * not the boolean keyword, and
10
+ * - `nullable: false` is flagged too (a boolean of either value counts as the
11
+ * keyword being present), so the migration fixer can drop it.
12
+ */
13
+ export declare const oasNoNullable: RulesetFunction;
@@ -0,0 +1,22 @@
1
+ import { isObject } from './helpers.js';
2
+ /**
3
+ * Flags a Schema Object's `nullable` keyword, removed in OpenAPI 3.1+ (JSON
4
+ * Schema 2020-12 uses a `"null"` type instead). Targets the *parent* of a
5
+ * `nullable` key (`$..nullable^`) rather than `$..nullable` directly, which fixes
6
+ * two problems with the naive `then: falsy` approach:
7
+ * - a property literally named `nullable` (`properties: { nullable: {...} }`)
8
+ * is no longer flagged, because there the `nullable` value is a Schema Object,
9
+ * not the boolean keyword, and
10
+ * - `nullable: false` is flagged too (a boolean of either value counts as the
11
+ * keyword being present), so the migration fixer can drop it.
12
+ */
13
+ export const oasNoNullable = (parent, _options, context) => {
14
+ if (!isObject(parent) || typeof parent['nullable'] !== 'boolean')
15
+ return [];
16
+ return [
17
+ {
18
+ message: 'nullable is not available in OpenAPI 3.1 or later; use a "null" type instead.',
19
+ path: [...context.path, 'nullable'],
20
+ },
21
+ ];
22
+ };
@@ -0,0 +1,3 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /** Validates OpenAPI v2 formData operations declare a compatible `consumes`. */
3
+ export declare const oasOpFormDataConsumeCheck: RulesetFunction;
@@ -0,0 +1,21 @@
1
+ import { isObject } from './helpers.js';
2
+ /** Validates OpenAPI v2 formData operations declare a compatible `consumes`. */
3
+ export const oasOpFormDataConsumeCheck = (operation, _options, context) => {
4
+ if (!isObject(operation))
5
+ return [];
6
+ const params = Array.isArray(operation['parameters']) ? operation['parameters'] : [];
7
+ const hasFormData = params.some((param) => isObject(param) && param['in'] === 'formData');
8
+ if (!hasFormData)
9
+ return [];
10
+ const consumes = Array.isArray(operation['consumes']) ? operation['consumes'] : [];
11
+ const ok = consumes.some((type) => type === 'application/x-www-form-urlencoded' || type === 'multipart/form-data');
12
+ if (!ok) {
13
+ return [
14
+ {
15
+ message: 'Operations with formData parameters must consume application/x-www-form-urlencoded or multipart/form-data',
16
+ path: [...context.path, 'consumes'],
17
+ },
18
+ ];
19
+ }
20
+ return [];
21
+ };
@@ -0,0 +1,3 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /** Ensures `operationId` values are unique across the document. */
3
+ export declare const oasOpIdUnique: RulesetFunction;
@@ -0,0 +1,29 @@
1
+ import { isObject, OPERATION_METHODS } from './helpers.js';
2
+ /** Ensures `operationId` values are unique across the document. */
3
+ export const oasOpIdUnique = (paths, _options, context) => {
4
+ if (!isObject(paths))
5
+ return [];
6
+ const seen = new Set();
7
+ const results = [];
8
+ for (const [path, item] of Object.entries(paths)) {
9
+ if (!isObject(item))
10
+ continue;
11
+ for (const [method, operation] of Object.entries(item)) {
12
+ // Only real operations carry an `operationId`; an `x-`extension object with
13
+ // an `operationId`-looking key would otherwise create a phantom duplicate.
14
+ if (!OPERATION_METHODS.has(method) || !isObject(operation))
15
+ continue;
16
+ const id = operation['operationId'];
17
+ if (typeof id !== 'string')
18
+ continue;
19
+ if (seen.has(id)) {
20
+ results.push({
21
+ message: `operationId "${id}" must be unique`,
22
+ path: [...context.path, path, method, 'operationId'],
23
+ });
24
+ }
25
+ seen.add(id);
26
+ }
27
+ }
28
+ return results;
29
+ };
@@ -0,0 +1,9 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /**
3
+ * Validates an operation's `parameters` array, mirroring Spectral's `oasOpParams`:
4
+ * flags duplicate `name`+`in` combinations, and — for OpenAPI 2.0, where the body
5
+ * is a parameter — flags more than one `in: body` parameter (even with different
6
+ * names) as well as mixing `in: body` with `in: formData`. `$ref` entries are
7
+ * skipped because their real `name`/`in` is only known after resolution.
8
+ */
9
+ export declare const oasOpParams: RulesetFunction;
@@ -0,0 +1,46 @@
1
+ import { isObject } from './helpers.js';
2
+ /**
3
+ * Validates an operation's `parameters` array, mirroring Spectral's `oasOpParams`:
4
+ * flags duplicate `name`+`in` combinations, and — for OpenAPI 2.0, where the body
5
+ * is a parameter — flags more than one `in: body` parameter (even with different
6
+ * names) as well as mixing `in: body` with `in: formData`. `$ref` entries are
7
+ * skipped because their real `name`/`in` is only known after resolution.
8
+ */
9
+ export const oasOpParams = (params, _options, context) => {
10
+ if (!Array.isArray(params))
11
+ return [];
12
+ const results = [];
13
+ const seen = new Set();
14
+ const bodyIndices = [];
15
+ const formDataIndices = [];
16
+ params.forEach((param, index) => {
17
+ if (!isObject(param) || '$ref' in param)
18
+ return;
19
+ if (param['name'] !== undefined && param['in'] !== undefined) {
20
+ const key = `${String(param['in'])}:${String(param['name'])}`;
21
+ if (seen.has(key)) {
22
+ results.push({
23
+ message: `Duplicate parameter "${String(param['name'])}" in "${String(param['in'])}"`,
24
+ path: [...context.path, index],
25
+ });
26
+ }
27
+ seen.add(key);
28
+ }
29
+ if (param['in'] === 'body')
30
+ bodyIndices.push(index);
31
+ else if (param['in'] === 'formData')
32
+ formDataIndices.push(index);
33
+ });
34
+ // OAS2 allows at most one body parameter, and body and formData are mutually
35
+ // exclusive within a single operation.
36
+ if (bodyIndices.length > 0 && formDataIndices.length > 0) {
37
+ results.push({ message: 'Operation must not have both "in:body" and "in:formData" parameters' });
38
+ }
39
+ for (let i = 1; i < bodyIndices.length; i++) {
40
+ results.push({
41
+ message: 'Operation must not have more than a single instance of the "in:body" parameter',
42
+ path: [...context.path, bodyIndices[i]],
43
+ });
44
+ }
45
+ return results;
46
+ };
@@ -0,0 +1,5 @@
1
+ import type { RulesetFunction } from '../../../core/index.js';
2
+ /** Validates that every referenced security scheme is defined. */
3
+ export declare const oasOpSecurityDefined: RulesetFunction<Record<string, unknown>, {
4
+ schemesPath: string[];
5
+ }>;