api-quality-spectral-ruleset 1.3.0-beta.5 → 1.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.
package/apq-spectral.yaml
CHANGED
|
@@ -213,7 +213,7 @@ rules:
|
|
|
213
213
|
enum: [float, double, decimal]
|
|
214
214
|
apiq:OAR017:
|
|
215
215
|
description: "Resource path should alternate static and parametrized parts."
|
|
216
|
-
message: "OAR017: Resource path should alternate static and parametrized parts
|
|
216
|
+
message: "OAR017: Resource path should alternate static and parametrized parts"
|
|
217
217
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR017.md"
|
|
218
218
|
severity: error
|
|
219
219
|
given: "$.paths.*~"
|
|
@@ -829,7 +829,7 @@ rules:
|
|
|
829
829
|
severity: "warn"
|
|
830
830
|
given:
|
|
831
831
|
- "$.paths.*.*[responses,requestBody]..content..schema..properties.*~"
|
|
832
|
-
- "$.paths.*.*.parameters[
|
|
832
|
+
- "$.paths.*.*.parameters[?(@.in=='body')].schema..properties.*~"
|
|
833
833
|
- "$.paths.*.*.responses[*].schema..properties.*~"
|
|
834
834
|
then:
|
|
835
835
|
function: pattern
|
|
@@ -1,90 +1,70 @@
|
|
|
1
|
+
const COMBINERS = ['allOf', 'oneOf', 'anyOf'];
|
|
2
|
+
|
|
3
|
+
const checkSchema = (schema) => {
|
|
4
|
+
if (!schema || typeof schema !== 'object') return false;
|
|
5
|
+
if (schema.example !== undefined || schema.examples !== undefined) return true;
|
|
6
|
+
if (schema.properties) return Object.values(schema.properties).some(checkSchema);
|
|
7
|
+
if (schema.items) return checkSchema(schema.items);
|
|
8
|
+
return COMBINERS.some(c => Array.isArray(schema[c]) && schema[c].some(checkSchema));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const hasExample = (node) => {
|
|
12
|
+
if (!node || typeof node !== 'object') return false;
|
|
13
|
+
if (node.example !== undefined || node.examples !== undefined) return true;
|
|
14
|
+
if (node.content) return Object.values(node.content).some(hasExample);
|
|
15
|
+
if (node.schema) return checkSchema(node.schema);
|
|
16
|
+
return !!(node.properties || node.items || node.type) && checkSchema(node);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const collectPropertyIssues = (schema, issues) => {
|
|
20
|
+
if (!schema || typeof schema !== 'object' || schema.$ref) return;
|
|
21
|
+
|
|
22
|
+
if (schema.type === 'array') {
|
|
23
|
+
schema.items && collectPropertyIssues(schema.items, issues);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (schema.properties) {
|
|
28
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
29
|
+
if (!propSchema || typeof propSchema !== 'object' || propSchema.$ref) continue;
|
|
30
|
+
const propType = propSchema.type;
|
|
31
|
+
if (propType === 'object' || propType === 'array' || (!propType && propSchema.properties)) {
|
|
32
|
+
collectPropertyIssues(propSchema, issues);
|
|
33
|
+
} else if (propType !== undefined && propSchema.example === undefined && propSchema.examples === undefined) {
|
|
34
|
+
issues.push({ message: `OAR031: Property '${propName}' is missing an example.` });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
COMBINERS.forEach(c => {
|
|
40
|
+
if (Array.isArray(schema[c])) schema[c].forEach(sub => collectPropertyIssues(sub, issues));
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
1
44
|
/**
|
|
2
45
|
* @param {object} given
|
|
3
46
|
* @param {object} options
|
|
4
47
|
* @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
|
|
5
48
|
*/
|
|
6
|
-
module.exports = (given, options, context)
|
|
49
|
+
module.exports = function oar031ExamplesCoverage(given, options, context) {
|
|
7
50
|
if (!given || typeof given !== 'object') return [];
|
|
8
51
|
|
|
9
52
|
const issues = [];
|
|
10
53
|
const nodePath = context.path || [];
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const checkSchema = (schema) => {
|
|
15
|
-
if (!schema || typeof schema !== 'object') return false;
|
|
16
|
-
if (schema.example !== undefined || schema.examples !== undefined) return true;
|
|
17
|
-
if (schema.properties && typeof schema.properties === 'object') {
|
|
18
|
-
return Object.values(schema.properties).some(prop => checkSchema(prop));
|
|
19
|
-
}
|
|
20
|
-
if (schema.items) return checkSchema(schema.items);
|
|
21
|
-
for (const combiner of ['allOf', 'oneOf', 'anyOf']) {
|
|
22
|
-
if (schema[combiner] && Array.isArray(schema[combiner])) {
|
|
23
|
-
if (schema[combiner].some(sub => checkSchema(sub))) return true;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const hasExample = (node) => {
|
|
30
|
-
if (!node || typeof node !== 'object') return false;
|
|
31
|
-
if (node.example !== undefined || node.examples !== undefined) return true;
|
|
32
|
-
if (node.content && typeof node.content === 'object') {
|
|
33
|
-
return Object.values(node.content).some(mediaType => hasExample(mediaType));
|
|
34
|
-
}
|
|
35
|
-
if (node.schema) return checkSchema(node.schema);
|
|
36
|
-
if (node.properties || node.items || node.type) return checkSchema(node);
|
|
37
|
-
return false;
|
|
38
|
-
};
|
|
54
|
+
const isParameter = nodePath.length >= 2 && nodePath.at(-2) === 'parameters';
|
|
55
|
+
const isBodyParam = isParameter && given.in === 'body';
|
|
39
56
|
|
|
40
57
|
if (!hasExample(given)) {
|
|
41
58
|
issues.push({ message: 'OAR031: Must have one or more examples defined' });
|
|
42
59
|
}
|
|
43
60
|
|
|
44
|
-
if (!isParameter) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
collectPropertyIssues(schema.items, [...path, 'items']);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (schema.properties && typeof schema.properties === 'object') {
|
|
54
|
-
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
55
|
-
if (!propSchema || typeof propSchema !== 'object' || propSchema.$ref) continue;
|
|
56
|
-
|
|
57
|
-
const propType = propSchema.type;
|
|
58
|
-
|
|
59
|
-
if (propType === 'object' || (!propType && propSchema.properties)) {
|
|
60
|
-
collectPropertyIssues(propSchema, [...path, 'properties', propName]);
|
|
61
|
-
} else if (propType === 'array') {
|
|
62
|
-
collectPropertyIssues(propSchema, [...path, 'properties', propName]);
|
|
63
|
-
} else {
|
|
64
|
-
if (propSchema.example === undefined && propSchema.examples === undefined) {
|
|
65
|
-
issues.push({ message: `OAR031: Property '${propName}' is missing an example.` });
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for (const combiner of ['allOf', 'oneOf', 'anyOf']) {
|
|
72
|
-
if (schema[combiner] && Array.isArray(schema[combiner])) {
|
|
73
|
-
schema[combiner].forEach((sub, i) => {
|
|
74
|
-
collectPropertyIssues(sub, [...path, combiner, i]);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
if (given.content && typeof given.content === 'object') {
|
|
81
|
-
for (const [mediaType, mediaTypeNode] of Object.entries(given.content)) {
|
|
82
|
-
if (mediaTypeNode && mediaTypeNode.schema) {
|
|
83
|
-
collectPropertyIssues(mediaTypeNode.schema, ['content', mediaType, 'schema']);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
61
|
+
if (!isParameter || isBodyParam) {
|
|
62
|
+
if (given.content) {
|
|
63
|
+
Object.values(given.content).forEach(mt => {
|
|
64
|
+
if (mt?.schema) collectPropertyIssues(mt.schema, issues);
|
|
65
|
+
});
|
|
86
66
|
} else if (given.schema) {
|
|
87
|
-
collectPropertyIssues(given.schema,
|
|
67
|
+
collectPropertyIssues(given.schema, issues);
|
|
88
68
|
}
|
|
89
69
|
}
|
|
90
70
|
|