Skip to content

Commit 50557a0

Browse files
feat: add functionality to handle anyOf and allOf checks
1 parent 3e11bc9 commit 50557a0

File tree

1 file changed

+57
-42
lines changed

1 file changed

+57
-42
lines changed

packages/core/src/rules/oas3/no-illogical-composition-keywords.ts

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -25,73 +25,88 @@ export const NoIllogicalCompositionKeywords: Oas3Rule = (): Oas3Visitor => {
2525
return {
2626
Schema: {
2727
skip(node) {
28-
return !node.oneOf;
28+
return !node.oneOf && !node.anyOf && !node.allOf;
2929
},
3030
enter(schema, { report, location, resolve }) {
31-
if (!Array.isArray(schema.oneOf)) return;
32-
const oneOfSchemas = schema.oneOf;
31+
// Helper function to get the composition keyword and schemas
32+
const compositionData = (() => {
33+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
34+
return { keyword: 'oneOf' as const, schemas: schema.oneOf };
35+
}
36+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
37+
return { keyword: 'anyOf' as const, schemas: schema.anyOf };
38+
}
39+
if (schema.allOf && Array.isArray(schema.allOf)) {
40+
return { keyword: 'allOf' as const, schemas: schema.allOf };
41+
}
42+
return null;
43+
})();
44+
45+
if (!compositionData) return;
3346

34-
if (oneOfSchemas.length < 2) {
47+
const { keyword, schemas } = compositionData;
48+
49+
// Check for minimum schema count (oneOf and anyOf require at least 2)
50+
if ((keyword === 'oneOf' || keyword === 'anyOf') && schemas.length < 2) {
3551
report({
36-
message: `Schema object \`oneOf\` should contain at least 2 schemas. Use the schema directly instead.`,
37-
location: location.child(['oneOf']),
52+
message: `Schema object \`${keyword}\` should contain at least 2 schemas. Use the schema directly instead.`,
53+
location: location.child([keyword]),
3854
});
3955
return;
4056
}
4157

42-
// Check for empty schema
43-
for (let i = 0; i < oneOfSchemas.length; i++) {
44-
const resolvedSchema = resolveSchema(oneOfSchemas[i], resolve);
58+
// Check for empty schemas (applies to all composition keywords)
59+
for (let i = 0; i < schemas.length; i++) {
60+
const resolvedSchema = resolveSchema(schemas[i], resolve);
4561
if (isEmptyObject(resolvedSchema)) {
4662
report({
47-
message:
48-
'Schema in `oneOf` is empty, which makes it impossible to discriminate between schemas.',
49-
location: location.child(['oneOf', String(i)]),
63+
message: `Schema in \`${keyword}\` is empty, which provides no validation constraints.`,
64+
location: location.child([keyword, String(i)]),
5065
});
5166
return;
5267
}
5368
}
5469

55-
// Check for nullable type
56-
// Check for impossible nullable + oneOf with null type combination
57-
if (hasNullableType(schema)) {
58-
const hasNullTypeInOneOf = oneOfSchemas.some((subSchema) => {
59-
const resolved = resolveSchema(subSchema, resolve);
60-
return hasNullableType(resolved);
61-
});
62-
63-
if (hasNullTypeInOneOf) {
70+
// Check for duplicate schemas
71+
for (let i = 0; i < schemas.length - 1; i++) {
72+
if (dequal(Object.values(schemas[i]), Object.values(schemas[i + 1]))) {
6473
report({
65-
message: `Schema with nullable type cannot have a oneOf option that is also nullable. This creates ambiguity when the value is null.`,
66-
location: location.child(['oneOf']),
74+
message: `Duplicate schemas found in \`${keyword}\`, which makes it impossible to discriminate between schemas.`,
75+
location: location.child([keyword]),
6776
});
68-
6977
return;
7078
}
7179
}
7280

73-
for (let i = 0; i < oneOfSchemas.length - 1; i++) {
74-
if (dequal(Object.values(oneOfSchemas[i]), Object.values(oneOfSchemas[i + 1]))) {
75-
report({
76-
message:
77-
'Duplicate schemas found in `oneOf`, which makes it impossible to discriminate between schemas.',
78-
location: location.child(['oneOf']),
81+
// oneOf-specific checks
82+
if (keyword === 'oneOf') {
83+
// Check for nullable type conflict
84+
if (hasNullableType(schema)) {
85+
const hasNullTypeInOneOf = schemas.some((subSchema) => {
86+
const resolved = resolveSchema(subSchema, resolve);
87+
return hasNullableType(resolved);
7988
});
8089

81-
return;
90+
if (hasNullTypeInOneOf) {
91+
report({
92+
message: `Schema with nullable type cannot have a oneOf option that is also nullable. This creates ambiguity when the value is null.`,
93+
location: location.child(['oneOf']),
94+
});
95+
return;
96+
}
8297
}
83-
}
8498

85-
// Always check mutual exclusivity - oneOf schemas should ALWAYS be mutually exclusive
86-
const { isExclusive, reasons } = areOneOfSchemasMutuallyExclusive(oneOfSchemas, resolve);
87-
88-
if (!isExclusive && reasons && reasons.length > 0) {
89-
// Report each ambiguous pair as a separate warning
90-
for (const reason of reasons) {
91-
report({
92-
message: reason,
93-
location: location.child(['oneOf']),
94-
});
99+
// Check mutual exclusivity - oneOf schemas should ALWAYS be mutually exclusive
100+
const { isExclusive, reasons } = areOneOfSchemasMutuallyExclusive(schemas, resolve);
101+
102+
if (!isExclusive && reasons && reasons.length > 0) {
103+
// Report each ambiguous pair as a separate warning
104+
for (const reason of reasons) {
105+
report({
106+
message: reason,
107+
location: location.child(['oneOf']),
108+
});
109+
}
95110
}
96111
}
97112
},

0 commit comments

Comments
 (0)