@@ -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