@redocly/openapi-core 1.0.0-beta.116 → 1.0.0-beta.118
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.
- package/lib/bundle.d.ts +1 -1
- package/lib/config/config-resolvers.js +13 -8
- package/lib/decorators/common/media-type-examples-override.d.ts +2 -0
- package/lib/decorators/common/media-type-examples-override.js +53 -0
- package/lib/decorators/oas3/index.d.ts +1 -0
- package/lib/decorators/oas3/index.js +2 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +3 -1
- package/lib/resolve.js +3 -0
- package/lib/rules/common/assertions/asserts.js +10 -2
- package/lib/rules/common/assertions/utils.js +12 -6
- package/lib/rules/common/spec.js +7 -4
- package/lib/rules/oas3/spec-components-invalid-map-name.js +26 -5
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +7 -1
- package/lib/types/oas2.js +16 -11
- package/lib/types/oas3.js +28 -11
- package/lib/types/oas3_1.js +6 -0
- package/lib/types/redocly-yaml.js +1 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +6 -1
- package/lib/visitors.d.ts +3 -1
- package/lib/visitors.js +4 -0
- package/lib/walk.js +8 -0
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +10 -0
- package/src/config/config-resolvers.ts +12 -8
- package/src/decorators/__tests__/media-type-examples-override.test.ts +665 -0
- package/src/decorators/__tests__/resources/request.yaml +3 -0
- package/src/decorators/__tests__/resources/response.yaml +3 -0
- package/src/decorators/common/media-type-examples-override.ts +79 -0
- package/src/decorators/oas3/index.ts +2 -0
- package/src/index.ts +2 -1
- package/src/resolve.ts +4 -1
- package/src/rules/common/assertions/__tests__/asserts.test.ts +89 -19
- package/src/rules/common/assertions/asserts.ts +10 -1
- package/src/rules/common/assertions/utils.ts +15 -6
- package/src/rules/common/spec.ts +8 -5
- package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +88 -0
- package/src/rules/oas3/spec-components-invalid-map-name.ts +26 -5
- package/src/types/index.ts +9 -0
- package/src/types/oas2.ts +16 -12
- package/src/types/oas3.ts +28 -12
- package/src/types/oas3_1.ts +6 -0
- package/src/types/redocly-yaml.ts +1 -0
- package/src/utils.ts +5 -0
- package/src/visitors.ts +7 -1
- package/src/walk.ts +15 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Oas3Decorator } from '../../visitors';
|
|
2
|
+
import { Oas3Operation, Oas3RequestBody, Oas3Response } from '../../typings/openapi';
|
|
3
|
+
import { yamlAndJsonSyncReader } from '../../utils';
|
|
4
|
+
import { isRef } from '../../ref-utils';
|
|
5
|
+
import { ResolveFn, UserContext } from '../../walk';
|
|
6
|
+
|
|
7
|
+
export const MediaTypeExamplesOverride: Oas3Decorator = ({ operationIds }) => {
|
|
8
|
+
return {
|
|
9
|
+
Operation: {
|
|
10
|
+
enter(operation: Oas3Operation, ctx: UserContext) {
|
|
11
|
+
const operationId = operation.operationId;
|
|
12
|
+
|
|
13
|
+
if (!operationId) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const properties = operationIds[operationId];
|
|
18
|
+
|
|
19
|
+
if (!properties) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (properties.responses && operation.responses) {
|
|
24
|
+
for (const responseCode of Object.keys(properties.responses)) {
|
|
25
|
+
const resolvedResponse = checkAndResolveRef<Oas3Response>(
|
|
26
|
+
operation.responses[responseCode],
|
|
27
|
+
ctx.resolve
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (!resolvedResponse) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
resolvedResponse.content = resolvedResponse.content ? resolvedResponse.content : {};
|
|
35
|
+
|
|
36
|
+
Object.keys(properties.responses[responseCode]).forEach((mimeType) => {
|
|
37
|
+
resolvedResponse.content![mimeType] = {
|
|
38
|
+
...resolvedResponse.content![mimeType],
|
|
39
|
+
examples: yamlAndJsonSyncReader(properties.responses[responseCode][mimeType]),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
operation.responses[responseCode] = resolvedResponse;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (properties.request && operation.requestBody) {
|
|
48
|
+
const resolvedRequest = checkAndResolveRef<Oas3RequestBody>(
|
|
49
|
+
operation.requestBody,
|
|
50
|
+
ctx.resolve
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!resolvedRequest) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
resolvedRequest.content = resolvedRequest.content ? resolvedRequest.content : {};
|
|
58
|
+
|
|
59
|
+
Object.keys(properties.request).forEach((mimeType) => {
|
|
60
|
+
resolvedRequest.content[mimeType] = {
|
|
61
|
+
...resolvedRequest.content[mimeType],
|
|
62
|
+
examples: yamlAndJsonSyncReader(properties.request[mimeType]),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
operation.requestBody = resolvedRequest;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function checkAndResolveRef<T>(node: any, resolver: ResolveFn): T | undefined {
|
|
73
|
+
if (!isRef(node)) {
|
|
74
|
+
return node;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const resolved = resolver<T>(node);
|
|
78
|
+
return resolved.error ? undefined : JSON.parse(JSON.stringify(resolved.node));
|
|
79
|
+
}
|
|
@@ -6,6 +6,7 @@ import { InfoDescriptionOverride } from '../common/info-description-override';
|
|
|
6
6
|
import { RemoveXInternal } from '../common/remove-x-internal';
|
|
7
7
|
import { FilterIn } from '../common/filters/filter-in';
|
|
8
8
|
import { FilterOut } from '../common/filters/filter-out';
|
|
9
|
+
import { MediaTypeExamplesOverride } from '../common/media-type-examples-override';
|
|
9
10
|
|
|
10
11
|
export const decorators = {
|
|
11
12
|
'registry-dependencies': RegistryDependencies as Oas3Decorator,
|
|
@@ -15,4 +16,5 @@ export const decorators = {
|
|
|
15
16
|
'remove-x-internal': RemoveXInternal as Oas3Decorator,
|
|
16
17
|
'filter-in': FilterIn as Oas3Decorator,
|
|
17
18
|
'filter-out': FilterOut as Oas3Decorator,
|
|
19
|
+
'media-type-examples-override': MediaTypeExamplesOverride as Oas3Decorator,
|
|
18
20
|
};
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ export {
|
|
|
36
36
|
CONFIG_FILE_NAMES,
|
|
37
37
|
RuleSeverity,
|
|
38
38
|
createConfig,
|
|
39
|
+
ResolvedApi,
|
|
39
40
|
} from './config';
|
|
40
41
|
|
|
41
42
|
export { RedoclyClient, isRedoclyRegistryURL } from './redocly';
|
|
@@ -50,7 +51,7 @@ export {
|
|
|
50
51
|
makeDocumentFromString,
|
|
51
52
|
} from './resolve';
|
|
52
53
|
export { parseYaml, stringifyYaml } from './js-yaml';
|
|
53
|
-
export { unescapePointer, isRef } from './ref-utils';
|
|
54
|
+
export { unescapePointer, isRef, isAbsoluteUrl } from './ref-utils';
|
|
54
55
|
export { detectOpenAPI, OasMajorVersion, openAPIMajor, OasVersion } from './oas-types';
|
|
55
56
|
export { normalizeVisitors } from './visitors';
|
|
56
57
|
|
package/src/resolve.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import { OasRef } from './typings/openapi';
|
|
4
4
|
import { isRef, joinPointer, escapePointer, parseRef, isAbsoluteUrl, isAnchor } from './ref-utils';
|
|
5
5
|
import type { YAMLNode, LoadOptions } from 'yaml-ast-parser';
|
|
6
|
-
import { NormalizedNodeType, isNamedType } from './types';
|
|
6
|
+
import { NormalizedNodeType, isNamedType, SpecExtension } from './types';
|
|
7
7
|
import { readFileFromUrl, parseYaml, nextTick } from './utils';
|
|
8
8
|
import { ResolveConfig } from './config/types';
|
|
9
9
|
|
|
@@ -276,6 +276,9 @@ export async function resolveDocument(opts: {
|
|
|
276
276
|
if (propType === undefined) propType = type.additionalProperties;
|
|
277
277
|
if (typeof propType === 'function') propType = propType(propValue, propName);
|
|
278
278
|
if (propType === undefined) propType = unknownType;
|
|
279
|
+
if (type.extensionsPrefix && propName.startsWith(type.extensionsPrefix)) {
|
|
280
|
+
propType = SpecExtension;
|
|
281
|
+
}
|
|
279
282
|
|
|
280
283
|
if (!isNamedType(propType) && propType?.directResolveAs) {
|
|
281
284
|
propType = propType.directResolveAs;
|
|
@@ -549,21 +549,25 @@ describe('oas3 assertions', () => {
|
|
|
549
549
|
});
|
|
550
550
|
});
|
|
551
551
|
|
|
552
|
-
describe
|
|
552
|
+
describe('sortOrder', () => {
|
|
553
553
|
it('value should be ordered in ASC direction', () => {
|
|
554
554
|
expect(asserts.sortOrder(['example', 'foo', 'test'], 'asc', baseLocation)).toEqual([]);
|
|
555
555
|
expect(
|
|
556
556
|
asserts.sortOrder(['example', 'foo', 'test'], { direction: 'asc' }, baseLocation)
|
|
557
557
|
).toEqual([]);
|
|
558
558
|
expect(asserts.sortOrder(['example'], 'asc', baseLocation)).toEqual([]);
|
|
559
|
-
expect(asserts.sortOrder(['example', 'test', 'foo'], 'asc', baseLocation)).toEqual(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
559
|
+
expect(asserts.sortOrder(['example', 'test', 'foo'], 'asc', baseLocation)).toEqual([
|
|
560
|
+
{
|
|
561
|
+
message: 'Should be sorted in an ascending order',
|
|
562
|
+
location: baseLocation,
|
|
563
|
+
},
|
|
564
|
+
]);
|
|
565
|
+
expect(asserts.sortOrder(['example', 'foo', 'test'], 'desc', baseLocation)).toEqual([
|
|
566
|
+
{
|
|
567
|
+
message: 'Should be sorted in a descending order',
|
|
568
|
+
location: baseLocation,
|
|
569
|
+
},
|
|
570
|
+
]);
|
|
567
571
|
expect(
|
|
568
572
|
asserts.sortOrder(
|
|
569
573
|
[{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }],
|
|
@@ -577,7 +581,12 @@ describe('oas3 assertions', () => {
|
|
|
577
581
|
{ direction: 'desc', property: 'name' },
|
|
578
582
|
baseLocation
|
|
579
583
|
)
|
|
580
|
-
).toEqual(
|
|
584
|
+
).toEqual([
|
|
585
|
+
{
|
|
586
|
+
message: 'Should be sorted in a descending order by property name',
|
|
587
|
+
location: baseLocation,
|
|
588
|
+
},
|
|
589
|
+
]);
|
|
581
590
|
});
|
|
582
591
|
it('value should be ordered in DESC direction', () => {
|
|
583
592
|
expect(asserts.sortOrder(['test', 'foo', 'example'], 'desc', baseLocation)).toEqual([]);
|
|
@@ -585,14 +594,18 @@ describe('oas3 assertions', () => {
|
|
|
585
594
|
asserts.sortOrder(['test', 'foo', 'example'], { direction: 'desc' }, baseLocation)
|
|
586
595
|
).toEqual([]);
|
|
587
596
|
expect(asserts.sortOrder(['example'], 'desc', baseLocation)).toEqual([]);
|
|
588
|
-
expect(asserts.sortOrder(['example', 'test', 'foo'], 'desc', baseLocation)).toEqual(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
597
|
+
expect(asserts.sortOrder(['example', 'test', 'foo'], 'desc', baseLocation)).toEqual([
|
|
598
|
+
{
|
|
599
|
+
message: 'Should be sorted in a descending order',
|
|
600
|
+
location: baseLocation,
|
|
601
|
+
},
|
|
602
|
+
]);
|
|
603
|
+
expect(asserts.sortOrder(['test', 'foo', 'example'], 'asc', baseLocation)).toEqual([
|
|
604
|
+
{
|
|
605
|
+
message: 'Should be sorted in an ascending order',
|
|
606
|
+
location: baseLocation,
|
|
607
|
+
},
|
|
608
|
+
]);
|
|
596
609
|
expect(
|
|
597
610
|
asserts.sortOrder(
|
|
598
611
|
[{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }],
|
|
@@ -606,7 +619,64 @@ describe('oas3 assertions', () => {
|
|
|
606
619
|
{ direction: 'asc', property: 'name' },
|
|
607
620
|
baseLocation
|
|
608
621
|
)
|
|
609
|
-
).toEqual(
|
|
622
|
+
).toEqual([
|
|
623
|
+
{
|
|
624
|
+
message: 'Should be sorted in an ascending order by property name',
|
|
625
|
+
location: baseLocation,
|
|
626
|
+
},
|
|
627
|
+
]);
|
|
628
|
+
});
|
|
629
|
+
it('should not order objects without property defined', () => {
|
|
630
|
+
expect(
|
|
631
|
+
asserts.sortOrder(
|
|
632
|
+
[
|
|
633
|
+
{ name: 'bar', id: 1 },
|
|
634
|
+
{ name: 'baz', id: 2 },
|
|
635
|
+
{ name: 'foo', id: 3 },
|
|
636
|
+
],
|
|
637
|
+
{ direction: 'desc' },
|
|
638
|
+
baseLocation
|
|
639
|
+
)
|
|
640
|
+
).toEqual([
|
|
641
|
+
{
|
|
642
|
+
message: 'Please define a property to sort objects by',
|
|
643
|
+
location: baseLocation,
|
|
644
|
+
},
|
|
645
|
+
]);
|
|
646
|
+
expect(
|
|
647
|
+
asserts.sortOrder(
|
|
648
|
+
[
|
|
649
|
+
{ name: 'bar', id: 1 },
|
|
650
|
+
{ name: 'baz', id: 2 },
|
|
651
|
+
{ name: 'foo', id: 3 },
|
|
652
|
+
],
|
|
653
|
+
{ direction: 'asc' },
|
|
654
|
+
baseLocation
|
|
655
|
+
)
|
|
656
|
+
).toEqual([
|
|
657
|
+
{
|
|
658
|
+
message: 'Please define a property to sort objects by',
|
|
659
|
+
location: baseLocation,
|
|
660
|
+
},
|
|
661
|
+
]);
|
|
662
|
+
});
|
|
663
|
+
it('should ignore string value casing while ordering', () => {
|
|
664
|
+
expect(asserts.sortOrder(['Example', 'foo', 'Test'], 'asc', baseLocation)).toEqual([]);
|
|
665
|
+
expect(asserts.sortOrder(['Test', 'foo', 'Example'], 'desc', baseLocation)).toEqual([]);
|
|
666
|
+
expect(
|
|
667
|
+
asserts.sortOrder(
|
|
668
|
+
[{ name: 'bar' }, { name: 'Baz' }, { name: 'Foo' }],
|
|
669
|
+
{ direction: 'asc', property: 'name' },
|
|
670
|
+
baseLocation
|
|
671
|
+
)
|
|
672
|
+
).toEqual([]);
|
|
673
|
+
expect(
|
|
674
|
+
asserts.sortOrder(
|
|
675
|
+
[{ name: 'Foo' }, { name: 'baz' }, { name: 'Bar' }],
|
|
676
|
+
{ direction: 'desc', property: 'name' },
|
|
677
|
+
baseLocation
|
|
678
|
+
)
|
|
679
|
+
).toEqual([]);
|
|
610
680
|
});
|
|
611
681
|
});
|
|
612
682
|
|
|
@@ -239,9 +239,18 @@ export const asserts: Asserts = {
|
|
|
239
239
|
condition: OrderOptions | OrderDirection,
|
|
240
240
|
baseLocation: Location
|
|
241
241
|
) => {
|
|
242
|
-
if (typeof value === 'undefined' || isOrdered(value, condition)) return [];
|
|
243
242
|
const direction = (condition as OrderOptions).direction || (condition as OrderDirection);
|
|
244
243
|
const property = (condition as OrderOptions).property;
|
|
244
|
+
if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && !property) {
|
|
245
|
+
return [
|
|
246
|
+
{
|
|
247
|
+
message: `Please define a property to sort objects by`,
|
|
248
|
+
location: baseLocation,
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
if (typeof value === 'undefined' || isOrdered(value, condition)) return [];
|
|
253
|
+
|
|
245
254
|
return [
|
|
246
255
|
{
|
|
247
256
|
message: `Should be sorted in ${
|
|
@@ -118,11 +118,11 @@ function applyAssertions(
|
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
} else {
|
|
121
|
-
const value =
|
|
121
|
+
const value = Array.isArray(node) ? node : Object.keys(node);
|
|
122
122
|
assertResults.push(
|
|
123
123
|
runAssertion({
|
|
124
|
-
values:
|
|
125
|
-
rawValues:
|
|
124
|
+
values: value,
|
|
125
|
+
rawValues: rawNode,
|
|
126
126
|
assert,
|
|
127
127
|
location: currentLocation,
|
|
128
128
|
})
|
|
@@ -288,11 +288,20 @@ export function isOrdered(value: any[], options: OrderOptions | OrderDirection):
|
|
|
288
288
|
let prevVal = value[i - 1];
|
|
289
289
|
|
|
290
290
|
if (property) {
|
|
291
|
-
|
|
291
|
+
const currPropValue = value[i][property];
|
|
292
|
+
const prevPropValue = value[i - 1][property];
|
|
293
|
+
|
|
294
|
+
if (!currPropValue || !prevPropValue) {
|
|
292
295
|
return false; // property doesn't exist, so collection is not ordered
|
|
293
296
|
}
|
|
294
|
-
|
|
295
|
-
|
|
297
|
+
|
|
298
|
+
currValue = currPropValue;
|
|
299
|
+
prevVal = prevPropValue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (typeof currValue === 'string' && typeof prevVal === 'string') {
|
|
303
|
+
currValue = currValue.toLowerCase();
|
|
304
|
+
prevVal = prevVal.toLowerCase();
|
|
296
305
|
}
|
|
297
306
|
|
|
298
307
|
const result = direction === 'asc' ? currValue >= prevVal : currValue <= prevVal;
|
package/src/rules/common/spec.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
-
import { isNamedType } from '../../types';
|
|
2
|
+
import { isNamedType, SpecExtension } from '../../types';
|
|
3
3
|
import { oasTypeOf, matchesJsonSchemaType, getSuggest, validateSchemaEnumType } from '../utils';
|
|
4
4
|
import { isRef } from '../../ref-utils';
|
|
5
5
|
import { isPlainObject } from '../../utils';
|
|
@@ -24,10 +24,13 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
24
24
|
}
|
|
25
25
|
return;
|
|
26
26
|
} else if (nodeType !== 'object') {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
if (type !== SpecExtension) {
|
|
28
|
+
// do not validate unknown extensions structure
|
|
29
|
+
report({
|
|
30
|
+
message: `Expected type \`${type.name}\` (object) but got \`${nodeType}\``,
|
|
31
|
+
from: refLocation,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
31
34
|
ignoreNextVisitorsOnNode();
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
@@ -214,4 +214,92 @@ describe('Oas3 spec-components-invalid-map-name', () => {
|
|
|
214
214
|
|
|
215
215
|
expect(results).toMatchInlineSnapshot(`Array []`);
|
|
216
216
|
});
|
|
217
|
+
|
|
218
|
+
it('should report about invalid keys inside nested examples', async () => {
|
|
219
|
+
const document = parseYamlToDocument(outdent`
|
|
220
|
+
openapi: 3.0.0
|
|
221
|
+
info:
|
|
222
|
+
version: 3.0.0
|
|
223
|
+
components:
|
|
224
|
+
parameters:
|
|
225
|
+
my Param:
|
|
226
|
+
name: param
|
|
227
|
+
description: param
|
|
228
|
+
in: path
|
|
229
|
+
examples:
|
|
230
|
+
invalid identifier:
|
|
231
|
+
description: 'Some description'
|
|
232
|
+
value: 21
|
|
233
|
+
`);
|
|
234
|
+
const results = await lintDocument({
|
|
235
|
+
externalRefResolver: new BaseResolver(),
|
|
236
|
+
document,
|
|
237
|
+
config: await makeConfig({
|
|
238
|
+
'spec-components-invalid-map-name': 'error',
|
|
239
|
+
}),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(results).toMatchInlineSnapshot(`
|
|
243
|
+
Array [
|
|
244
|
+
Object {
|
|
245
|
+
"location": Array [
|
|
246
|
+
Object {
|
|
247
|
+
"pointer": "#/components/parameters/my Param",
|
|
248
|
+
"reportOnKey": true,
|
|
249
|
+
"source": Source {
|
|
250
|
+
"absoluteRef": "",
|
|
251
|
+
"body": "openapi: 3.0.0
|
|
252
|
+
info:
|
|
253
|
+
version: 3.0.0
|
|
254
|
+
components:
|
|
255
|
+
parameters:
|
|
256
|
+
my Param:
|
|
257
|
+
name: param
|
|
258
|
+
description: param
|
|
259
|
+
in: path
|
|
260
|
+
examples:
|
|
261
|
+
invalid identifier:
|
|
262
|
+
description: 'Some description'
|
|
263
|
+
value: 21 ",
|
|
264
|
+
"mimeType": undefined,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
"message": "The map key in parameters \\"my Param\\" does not match the regular expression \\"^[a-zA-Z0-9\\\\.\\\\-_]+$\\"",
|
|
269
|
+
"ruleId": "spec-components-invalid-map-name",
|
|
270
|
+
"severity": "error",
|
|
271
|
+
"suggest": Array [],
|
|
272
|
+
},
|
|
273
|
+
Object {
|
|
274
|
+
"location": Array [
|
|
275
|
+
Object {
|
|
276
|
+
"pointer": "#/components/parameters/my Param/examples/invalid identifier",
|
|
277
|
+
"reportOnKey": true,
|
|
278
|
+
"source": Source {
|
|
279
|
+
"absoluteRef": "",
|
|
280
|
+
"body": "openapi: 3.0.0
|
|
281
|
+
info:
|
|
282
|
+
version: 3.0.0
|
|
283
|
+
components:
|
|
284
|
+
parameters:
|
|
285
|
+
my Param:
|
|
286
|
+
name: param
|
|
287
|
+
description: param
|
|
288
|
+
in: path
|
|
289
|
+
examples:
|
|
290
|
+
invalid identifier:
|
|
291
|
+
description: 'Some description'
|
|
292
|
+
value: 21 ",
|
|
293
|
+
"mimeType": undefined,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
"message": "The map key in examples \\"invalid identifier\\" does not match the regular expression \\"^[a-zA-Z0-9\\\\.\\\\-_]+$\\"",
|
|
298
|
+
"ruleId": "spec-components-invalid-map-name",
|
|
299
|
+
"severity": "error",
|
|
300
|
+
"suggest": Array [],
|
|
301
|
+
},
|
|
302
|
+
]
|
|
303
|
+
`);
|
|
304
|
+
});
|
|
217
305
|
});
|
|
@@ -20,34 +20,55 @@ export const SpecComponentsInvalidMapName: Oas3Rule = () => {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
return {
|
|
23
|
-
|
|
23
|
+
NamedSchemas: {
|
|
24
|
+
Schema(_node, { key, report, location }: UserContext) {
|
|
25
|
+
validateKey(key, report, location, 'schemas');
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
NamedParameters: {
|
|
24
29
|
Parameter(_node, { key, report, location }: UserContext) {
|
|
25
30
|
validateKey(key, report, location, 'parameters');
|
|
26
31
|
},
|
|
32
|
+
},
|
|
33
|
+
NamedResponses: {
|
|
27
34
|
Response(_node, { key, report, location }: UserContext) {
|
|
28
35
|
validateKey(key, report, location, 'responses');
|
|
29
36
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
37
|
+
},
|
|
38
|
+
NamedExamples: {
|
|
33
39
|
Example(_node, { key, report, location }: UserContext) {
|
|
34
40
|
validateKey(key, report, location, 'examples');
|
|
35
41
|
},
|
|
42
|
+
},
|
|
43
|
+
NamedRequestBodies: {
|
|
36
44
|
RequestBody(_node, { key, report, location }: UserContext) {
|
|
37
45
|
validateKey(key, report, location, 'requestBodies');
|
|
38
46
|
},
|
|
47
|
+
},
|
|
48
|
+
NamedHeaders: {
|
|
39
49
|
Header(_node, { key, report, location }: UserContext) {
|
|
40
50
|
validateKey(key, report, location, 'headers');
|
|
41
51
|
},
|
|
52
|
+
},
|
|
53
|
+
NamedSecuritySchemes: {
|
|
42
54
|
SecurityScheme(_node, { key, report, location }: UserContext) {
|
|
43
|
-
validateKey(key, report, location, '
|
|
55
|
+
validateKey(key, report, location, 'securitySchemes');
|
|
44
56
|
},
|
|
57
|
+
},
|
|
58
|
+
NamedLinks: {
|
|
45
59
|
Link(_node, { key, report, location }: UserContext) {
|
|
46
60
|
validateKey(key, report, location, 'links');
|
|
47
61
|
},
|
|
62
|
+
},
|
|
63
|
+
NamedCallbacks: {
|
|
48
64
|
Callback(_node, { key, report, location }: UserContext) {
|
|
49
65
|
validateKey(key, report, location, 'callbacks');
|
|
50
66
|
},
|
|
51
67
|
},
|
|
68
|
+
ExampleMap: {
|
|
69
|
+
Example(_node, { key, report, location }: UserContext) {
|
|
70
|
+
validateKey(key, report, location, 'examples');
|
|
71
|
+
},
|
|
72
|
+
},
|
|
52
73
|
};
|
|
53
74
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -63,6 +63,11 @@ export function mapOf(typeName: string) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export const SpecExtension: NormalizedNodeType = {
|
|
67
|
+
name: 'SpecExtension',
|
|
68
|
+
properties: {},
|
|
69
|
+
};
|
|
70
|
+
|
|
66
71
|
export function normalizeTypes(
|
|
67
72
|
types: Record<string, NodeType>,
|
|
68
73
|
options: { doNotResolveExamples?: boolean } = {}
|
|
@@ -79,6 +84,10 @@ export function normalizeTypes(
|
|
|
79
84
|
for (const type of Object.values(normalizedTypes)) {
|
|
80
85
|
normalizeType(type);
|
|
81
86
|
}
|
|
87
|
+
|
|
88
|
+
// all type trees have a SpecExtension type by default
|
|
89
|
+
normalizedTypes['SpecExtension'] = SpecExtension;
|
|
90
|
+
|
|
82
91
|
return normalizedTypes;
|
|
83
92
|
|
|
84
93
|
function normalizeType(type: NormalizedNodeType) {
|
package/src/types/oas2.ts
CHANGED
|
@@ -24,6 +24,7 @@ const Root: NodeType = {
|
|
|
24
24
|
'x-ignoredHeaderParameters': { type: 'array', items: { type: 'string' } },
|
|
25
25
|
},
|
|
26
26
|
required: ['swagger', 'paths', 'info'],
|
|
27
|
+
extensionsPrefix: 'x-',
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
const Info: NodeType = {
|
|
@@ -37,6 +38,7 @@ const Info: NodeType = {
|
|
|
37
38
|
'x-logo': 'Logo',
|
|
38
39
|
},
|
|
39
40
|
required: ['title', 'version'],
|
|
41
|
+
extensionsPrefix: 'x-',
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
const Logo: NodeType = {
|
|
@@ -46,6 +48,7 @@ const Logo: NodeType = {
|
|
|
46
48
|
backgroundColor: { type: 'string' },
|
|
47
49
|
href: { type: 'string' },
|
|
48
50
|
},
|
|
51
|
+
extensionsPrefix: 'x-',
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
const Contact: NodeType = {
|
|
@@ -54,6 +57,7 @@ const Contact: NodeType = {
|
|
|
54
57
|
url: { type: 'string' },
|
|
55
58
|
email: { type: 'string' },
|
|
56
59
|
},
|
|
60
|
+
extensionsPrefix: 'x-',
|
|
57
61
|
};
|
|
58
62
|
|
|
59
63
|
const License: NodeType = {
|
|
@@ -62,6 +66,7 @@ const License: NodeType = {
|
|
|
62
66
|
url: { type: 'string' },
|
|
63
67
|
},
|
|
64
68
|
required: ['name'],
|
|
69
|
+
extensionsPrefix: 'x-',
|
|
65
70
|
};
|
|
66
71
|
|
|
67
72
|
const Paths: NodeType = {
|
|
@@ -83,6 +88,7 @@ const PathItem: NodeType = {
|
|
|
83
88
|
head: 'Operation',
|
|
84
89
|
patch: 'Operation',
|
|
85
90
|
},
|
|
91
|
+
extensionsPrefix: 'x-',
|
|
86
92
|
};
|
|
87
93
|
|
|
88
94
|
const Operation: NodeType = {
|
|
@@ -102,9 +108,9 @@ const Operation: NodeType = {
|
|
|
102
108
|
'x-codeSamples': 'XCodeSampleList',
|
|
103
109
|
'x-code-samples': 'XCodeSampleList', // deprecated
|
|
104
110
|
'x-hideTryItPanel': { type: 'boolean' },
|
|
105
|
-
'x-meta': 'XMetaList',
|
|
106
111
|
},
|
|
107
112
|
required: ['responses'],
|
|
113
|
+
extensionsPrefix: 'x-',
|
|
108
114
|
};
|
|
109
115
|
|
|
110
116
|
const XCodeSample: NodeType = {
|
|
@@ -129,6 +135,7 @@ const ExternalDocs: NodeType = {
|
|
|
129
135
|
url: { type: 'string' },
|
|
130
136
|
},
|
|
131
137
|
required: ['url'],
|
|
138
|
+
extensionsPrefix: 'x-',
|
|
132
139
|
};
|
|
133
140
|
|
|
134
141
|
const Parameter: NodeType = {
|
|
@@ -173,6 +180,7 @@ const Parameter: NodeType = {
|
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
182
|
},
|
|
183
|
+
extensionsPrefix: 'x-',
|
|
176
184
|
};
|
|
177
185
|
|
|
178
186
|
const ParameterItems: NodeType = {
|
|
@@ -202,6 +210,7 @@ const ParameterItems: NodeType = {
|
|
|
202
210
|
return ['type'];
|
|
203
211
|
}
|
|
204
212
|
},
|
|
213
|
+
extensionsPrefix: 'x-',
|
|
205
214
|
};
|
|
206
215
|
|
|
207
216
|
const Responses: NodeType = {
|
|
@@ -221,6 +230,7 @@ const Response: NodeType = {
|
|
|
221
230
|
'x-summary': { type: 'string' },
|
|
222
231
|
},
|
|
223
232
|
required: ['description'],
|
|
233
|
+
extensionsPrefix: 'x-',
|
|
224
234
|
};
|
|
225
235
|
|
|
226
236
|
const Examples: NodeType = {
|
|
@@ -256,6 +266,7 @@ const Header: NodeType = {
|
|
|
256
266
|
return ['type'];
|
|
257
267
|
}
|
|
258
268
|
},
|
|
269
|
+
extensionsPrefix: 'x-',
|
|
259
270
|
};
|
|
260
271
|
|
|
261
272
|
const Tag: NodeType = {
|
|
@@ -267,6 +278,7 @@ const Tag: NodeType = {
|
|
|
267
278
|
'x-displayName': { type: 'string' },
|
|
268
279
|
},
|
|
269
280
|
required: ['name'],
|
|
281
|
+
extensionsPrefix: 'x-',
|
|
270
282
|
};
|
|
271
283
|
|
|
272
284
|
const TagGroup: NodeType = {
|
|
@@ -329,6 +341,7 @@ const Schema: NodeType = {
|
|
|
329
341
|
'x-explicitMappingOnly': { type: 'boolean' },
|
|
330
342
|
'x-enumDescriptions': 'EnumDescriptions',
|
|
331
343
|
},
|
|
344
|
+
extensionsPrefix: 'x-',
|
|
332
345
|
};
|
|
333
346
|
|
|
334
347
|
const EnumDescriptions: NodeType = {
|
|
@@ -349,6 +362,7 @@ const Xml: NodeType = {
|
|
|
349
362
|
attribute: { type: 'boolean' },
|
|
350
363
|
wrapped: { type: 'boolean' },
|
|
351
364
|
},
|
|
365
|
+
extensionsPrefix: 'x-',
|
|
352
366
|
};
|
|
353
367
|
|
|
354
368
|
const SecurityScheme: NodeType = {
|
|
@@ -420,15 +434,7 @@ const Example: NodeType = {
|
|
|
420
434
|
description: { type: 'string' },
|
|
421
435
|
externalValue: { type: 'string' },
|
|
422
436
|
},
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const XMeta: NodeType = {
|
|
426
|
-
properties: {
|
|
427
|
-
title: { type: 'string' },
|
|
428
|
-
description: { type: 'string' },
|
|
429
|
-
keywords: { type: 'string' },
|
|
430
|
-
image: { type: 'string' },
|
|
431
|
-
},
|
|
437
|
+
extensionsPrefix: 'x-',
|
|
432
438
|
};
|
|
433
439
|
|
|
434
440
|
export const Oas2Types: Record<string, NodeType> = {
|
|
@@ -467,8 +473,6 @@ export const Oas2Types: Record<string, NodeType> = {
|
|
|
467
473
|
SecurityScheme,
|
|
468
474
|
XCodeSample,
|
|
469
475
|
XCodeSampleList: listOf('XCodeSample'),
|
|
470
|
-
XMeta,
|
|
471
|
-
XMetaList: listOf('XMeta'),
|
|
472
476
|
XServerList: listOf('XServer'),
|
|
473
477
|
XServer,
|
|
474
478
|
};
|