Skip to content

Commit 61030b8

Browse files
committed
CLOUDP-306581: base types oneof
1 parent 9dd553e commit 61030b8

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
const componentSchemas = {
5+
schemas: {
6+
Dog: {
7+
type: 'object',
8+
properties: {
9+
breed: { type: 'string' },
10+
age: { type: 'integer' },
11+
},
12+
},
13+
Cat: {
14+
type: 'object',
15+
properties: {
16+
color: { type: 'string' },
17+
livesLeft: { type: 'integer' },
18+
},
19+
},
20+
},
21+
};
22+
23+
testRule('xgen-IPA-125-oneOf-no-base-types', [
24+
{
25+
name: 'valid oneOf with references only',
26+
document: {
27+
components: componentSchemas,
28+
schemas: {
29+
Animal: {
30+
oneOf: [{ $ref: '#/components/schemas/Dog' }, { $ref: '#/components/schemas/Cat' }],
31+
},
32+
},
33+
},
34+
errors: [],
35+
},
36+
{
37+
name: 'valid oneOf with object schema',
38+
document: {
39+
components: componentSchemas,
40+
schemas: {
41+
MixedObject: {
42+
oneOf: [
43+
{
44+
type: 'object',
45+
properties: {
46+
name: { type: 'string' },
47+
},
48+
},
49+
{ $ref: '#/components/schemas/Dog' },
50+
],
51+
},
52+
},
53+
},
54+
errors: [],
55+
},
56+
{
57+
name: 'invalid oneOf with string type',
58+
document: {
59+
components: componentSchemas,
60+
schemas: {
61+
MixedType: {
62+
oneOf: [{ type: 'string' }, { $ref: '#/components/schemas/Dog' }],
63+
},
64+
},
65+
},
66+
errors: [
67+
{
68+
code: 'xgen-IPA-125-oneOf-no-base-types',
69+
message: 'oneOf should not contain base types like integer, number, string, or boolean.',
70+
path: ['schemas', 'MixedType'],
71+
severity: DiagnosticSeverity.Error,
72+
},
73+
],
74+
},
75+
{
76+
name: 'invalid oneOf with multiple base types',
77+
document: {
78+
components: componentSchemas,
79+
schemas: {
80+
BaseTypes: {
81+
oneOf: [{ type: 'string' }, { type: 'integer' }, { type: 'boolean' }],
82+
},
83+
},
84+
},
85+
errors: [
86+
{
87+
code: 'xgen-IPA-125-oneOf-no-base-types',
88+
message: 'oneOf should not contain base types like integer, number, string, or boolean.',
89+
path: ['schemas', 'BaseTypes'],
90+
severity: DiagnosticSeverity.Error,
91+
},
92+
],
93+
},
94+
{
95+
name: 'oneOf with exception',
96+
document: {
97+
components: componentSchemas,
98+
schemas: {
99+
MixedType: {
100+
oneOf: [{ type: 'string' }, { type: 'integer' }],
101+
'x-xgen-IPA-exception': {
102+
'xgen-IPA-125-oneOf-no-base-types': 'reason for exemption',
103+
},
104+
},
105+
},
106+
},
107+
errors: [],
108+
},
109+
]);

tools/spectral/ipa/rulesets/IPA-125.yaml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
functions:
55
- IPA125OneOfMustHaveDiscriminator
6+
- IPA125OneOfNoBaseTypes
67

78
rules:
89
xgen-IPA-125-oneOf-must-have-discriminator:
@@ -39,3 +40,51 @@ rules:
3940
given: '$..[?(@.oneOf)]'
4041
then:
4142
function: 'IPA125OneOfMustHaveDiscriminator'
43+
44+
xgen-IPA-125-oneOf-no-base-types:
45+
description: |
46+
API producers should not use oneOf with base types like integer, string, boolean, or number.
47+
48+
##### Implementation details
49+
Rule checks for the following conditions:
50+
- Applies to schemas with `oneOf` arrays
51+
- Ensures no element within oneOf has a type property that is a primitive/base type
52+
- Base types considered are: integer, string, boolean, number
53+
54+
##### Rationale
55+
Using oneOf with primitive types can lead to ambiguity and validation problems. Clients may not
56+
be able to properly determine which type to use in which context. Instead, use more specific
57+
object types with clear discriminators.
58+
59+
##### Example Violation
60+
```yaml
61+
# Incorrect - Using oneOf with base types
62+
type: object
63+
properties:
64+
value:
65+
oneOf:
66+
- type: string
67+
- type: integer
68+
```
69+
70+
##### Example Compliance
71+
```yaml
72+
# Correct - Using oneOf with object types only
73+
type: object
74+
properties:
75+
value:
76+
oneOf:
77+
- $ref: '#/components/schemas/StringValue'
78+
- $ref: '#/components/schemas/IntegerValue'
79+
discriminator:
80+
propertyName: valueType
81+
mapping:
82+
string: '#/components/schemas/StringValue'
83+
integer: '#/components/schemas/IntegerValue'
84+
```
85+
86+
message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-125-oneOf-no-base-types'
87+
severity: error
88+
given: '$..[?(@.oneOf)]'
89+
then:
90+
function: 'IPA125OneOfNoBaseTypes'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { collectAdoption, collectAndReturnViolation } from './utils/collectionUtils.js';
2+
import { resolveObject } from './utils/componentUtils.js';
3+
import { hasException } from './utils/exceptions.js';
4+
5+
const RULE_NAME = 'xgen-IPA-125-oneOf-no-base-types';
6+
const ERROR_MESSAGE = 'oneOf should not contain base types like integer, number, string, or boolean.';
7+
8+
export default (input, _, { path, documentInventory }) => {
9+
const oas = documentInventory.unresolved; // Use unresolved document to access raw $ref
10+
const schema = resolveObject(oas, path);
11+
12+
if (!schema || !schema.oneOf || !Array.isArray(schema.oneOf)) {
13+
return;
14+
}
15+
16+
// Check for exception first
17+
if (hasException(schema, RULE_NAME)) {
18+
return;
19+
}
20+
21+
// Check if any oneOf item is a base type
22+
const baseTypes = ['integer', 'number', 'string', 'boolean'];
23+
const hasBaseType = schema.oneOf.some(
24+
(item) => typeof item === 'object' && item.type && baseTypes.includes(item.type)
25+
);
26+
27+
if (hasBaseType) {
28+
return collectAndReturnViolation(path, RULE_NAME, [{ message: ERROR_MESSAGE }]);
29+
}
30+
31+
collectAdoption(path, RULE_NAME);
32+
};

0 commit comments

Comments
 (0)