Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 47223e4

Browse files
committed
feat(oas3): add support for Schema Object 'oneOf'
1 parent aa85066 commit 47223e4

File tree

4 files changed

+211
-3
lines changed

4 files changed

+211
-3
lines changed

packages/openapi3-parser/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Fury OAS3 Parser Changelog
22

3+
## Master
4+
5+
### Enhancements
6+
7+
- Adds partial support for using `oneOf` in a Schema Object. One of is
8+
supported when used in a schema object alone, or with the nullable constraint
9+
or any annotation. It is not supported in the case when one of is used in
10+
conjunction with other constraints in the same schema object.
11+
312
## 0.13.1 (2020-06-22)
413

514
### Bug Fixes

packages/openapi3-parser/STATUS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ support.
213213
| minProperties ||
214214
| required ||
215215
| allOf ||
216-
| oneOf | |
216+
| oneOf | ~ |
217217
| anyOf ||
218218
| not ||
219219
| items ||

packages/openapi3-parser/lib/parser/oas/parseSchemaObject.js

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const unsupportedKeys = [
2323
'minItems', 'uniqueItems', 'maxProperties', 'minProperties', 'required',
2424

2525
// JSON Schema + OAS 3 specific rules
26-
'allOf', 'oneOf', 'anyOf', 'not', 'additionalProperties', 'format',
26+
'allOf', 'anyOf', 'not', 'additionalProperties', 'format',
2727

2828
// OAS 3 specific
2929
'discriminator', 'readOnly', 'writeOnly', 'xml', 'externalDocs', 'deprecated',
@@ -124,6 +124,51 @@ function validateValuesMatchSchema(context, schema) {
124124
return parseObject(context, name, parseMember)(schema);
125125
}
126126

127+
// Warns if oneOf is used with other unsupported constraints
128+
function validateOneOfIsNotUsedWithUnsupportedConstraints(context) {
129+
// oneOf can be used like the following:
130+
//
131+
// oneOf:
132+
// - ...
133+
// - ...
134+
// ...
135+
//
136+
// where its effectively a combination of "one of these two constraints"
137+
// and "also these other constraits" which is effectively:
138+
//
139+
// allOf:
140+
// - oneOf:
141+
// - ...
142+
// - ...
143+
// - ...
144+
//
145+
// API Element's doesn't have a way to support `allOf` and thus using
146+
// `oneOf` alongside other constraints is unsupported.
147+
//
148+
// We can allow annotations and (nullable as that is simple to support).
149+
150+
// is a JSON Schema annotation (not constraint)
151+
const isAnnotation = R.anyPass([
152+
hasKey('title'),
153+
hasKey('description'),
154+
hasKey('default'),
155+
hasKey('example'),
156+
]);
157+
158+
const createUnsupportedWithOneOfWarning = member => createWarning(context.namespace,
159+
`'${name}' has limited support for 'oneOf', use of '${member.key.toValue()}' with 'oneOf' is not supported`,
160+
member.key);
161+
162+
const parseMember = R.cond([
163+
[hasKey('oneOf'), R.identity],
164+
[hasKey('nullable'), R.identity],
165+
[isAnnotation, R.identity],
166+
[R.T, createUnsupportedWithOneOfWarning],
167+
]);
168+
169+
return parseObject(context, name, parseMember);
170+
}
171+
127172
function parseSchema(context) {
128173
const { namespace } = context;
129174

@@ -146,6 +191,14 @@ function parseSchema(context) {
146191
createWarning(namespace, `'${name}' 'required' array value is not a string`));
147192
const parseRequired = parseArray(context, `${name}' 'required`, parseRequiredString);
148193

194+
const parseOneOf = pipeParseResult(namespace,
195+
parseArray(context, `${name}' 'oneOf`, parseSubSchema),
196+
(oneOf) => {
197+
const element = new namespace.elements.Enum();
198+
element.enumerations = oneOf;
199+
return element;
200+
});
201+
149202
const parseMember = R.cond([
150203
[hasKey('type'), parseType],
151204
[hasKey('enum'), R.compose(parseEnum(context, name), getValue)],
@@ -156,6 +209,7 @@ function parseSchema(context) {
156209
[hasKey('description'), parseString(context, name, false)],
157210
[hasKey('default'), e => e.clone()],
158211
[hasKey('example'), e => e.clone()],
212+
[hasKey('oneOf'), R.compose(parseOneOf, getValue)],
159213

160214
[isUnsupportedKey, createUnsupportedMemberWarning(namespace, name)],
161215

@@ -166,13 +220,17 @@ function parseSchema(context) {
166220
return pipeParseResult(namespace,
167221
parseObject(context, name, parseMember),
168222
R.curry(validateValuesMatchSchema)(context),
223+
R.when(object => object.hasKey('oneOf'), validateOneOfIsNotUsedWithUnsupportedConstraints(context)),
169224
(schema) => {
170225
let element;
171226

227+
const oneOf = schema.get('oneOf');
172228
const enumerations = schema.get('enum');
173229
const type = schema.getValue('type');
174230

175-
if (enumerations) {
231+
if (oneOf) {
232+
element = oneOf;
233+
} else if (enumerations) {
176234
element = enumerations;
177235
} else if (type === 'object') {
178236
element = constructObjectStructure(namespace, schema);

packages/openapi3-parser/test/unit/parser/oas/parseSchemaObject-test.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,4 +822,145 @@ describe('Schema Object', () => {
822822
"'Schema Object' 'default' does not match expected type 'number'",
823823
]);
824824
});
825+
826+
describe('#oneOf', () => {
827+
it('can parse oneOf', () => {
828+
const schema = new namespace.elements.Object({
829+
oneOf: [
830+
{ type: 'string' },
831+
{ type: 'number' },
832+
],
833+
});
834+
const parseResult = parse(context, schema);
835+
836+
expect(parseResult.length).to.equal(1);
837+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
838+
expect(parseResult).to.not.contain.annotations;
839+
840+
const element = parseResult.get(0).content;
841+
expect(element).to.be.instanceof(namespace.elements.Enum);
842+
843+
expect(element.enumerations.length).to.equal(2);
844+
expect(element.enumerations.get(0)).to.be.instanceof(namespace.elements.String);
845+
expect(element.enumerations.get(1)).to.be.instanceof(namespace.elements.Number);
846+
});
847+
848+
it('can parse oneOf with description', () => {
849+
const schema = new namespace.elements.Object({
850+
oneOf: [
851+
{ type: 'string' },
852+
{ type: 'number' },
853+
],
854+
description: 'string or number',
855+
});
856+
const parseResult = parse(context, schema);
857+
858+
expect(parseResult.length).to.equal(1);
859+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
860+
expect(parseResult).to.not.contain.annotations;
861+
862+
const element = parseResult.get(0).content;
863+
expect(element).to.be.instanceof(namespace.elements.Enum);
864+
expect(element.description.toValue()).to.equal('string or number');
865+
866+
expect(element.enumerations.length).to.equal(2);
867+
expect(element.enumerations.get(0)).to.be.instanceof(namespace.elements.String);
868+
expect(element.enumerations.get(1)).to.be.instanceof(namespace.elements.Number);
869+
});
870+
871+
it('can parse oneOf with default', () => {
872+
const schema = new namespace.elements.Object({
873+
oneOf: [
874+
{ type: 'string' },
875+
{ type: 'number' },
876+
],
877+
default: 'unset',
878+
});
879+
const parseResult = parse(context, schema);
880+
881+
expect(parseResult.length).to.equal(1);
882+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
883+
expect(parseResult).to.not.contain.annotations;
884+
885+
const element = parseResult.get(0).content;
886+
expect(element).to.be.instanceof(namespace.elements.Enum);
887+
expect(element.attributes.getValue('default')).to.equal('unset');
888+
889+
expect(element.enumerations.length).to.equal(2);
890+
expect(element.enumerations.get(0)).to.be.instanceof(namespace.elements.String);
891+
expect(element.enumerations.get(1)).to.be.instanceof(namespace.elements.Number);
892+
});
893+
894+
it('can parse oneOf with example', () => {
895+
const schema = new namespace.elements.Object({
896+
oneOf: [
897+
{ type: 'string' },
898+
{ type: 'number' },
899+
],
900+
example: 52,
901+
});
902+
const parseResult = parse(context, schema);
903+
904+
expect(parseResult.length).to.equal(1);
905+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
906+
expect(parseResult).to.not.contain.annotations;
907+
908+
const element = parseResult.get(0).content;
909+
expect(element).to.be.instanceof(namespace.elements.Enum);
910+
expect(element.attributes.getValue('samples')).to.deep.equal([52]);
911+
912+
expect(element.enumerations.length).to.equal(2);
913+
expect(element.enumerations.get(0)).to.be.instanceof(namespace.elements.String);
914+
expect(element.enumerations.get(1)).to.be.instanceof(namespace.elements.Number);
915+
});
916+
917+
it('can parse oneOf with nullable', () => {
918+
const schema = new namespace.elements.Object({
919+
oneOf: [
920+
{ type: 'string' },
921+
{ type: 'number' },
922+
],
923+
nullable: true,
924+
});
925+
const parseResult = parse(context, schema);
926+
927+
expect(parseResult.length).to.equal(1);
928+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
929+
expect(parseResult).to.not.contain.annotations;
930+
931+
const element = parseResult.get(0).content;
932+
expect(element).to.be.instanceof(namespace.elements.Enum);
933+
expect(element.attributes.getValue('typeAttributes')).to.deep.equal(['nullable']);
934+
935+
expect(element.enumerations.length).to.equal(2);
936+
expect(element.enumerations.get(0)).to.be.instanceof(namespace.elements.String);
937+
expect(element.enumerations.get(1)).to.be.instanceof(namespace.elements.Number);
938+
});
939+
940+
it('warns for unsupported oneOf with other schema properties', () => {
941+
const schema = new namespace.elements.Object({
942+
oneOf: [
943+
{ type: 'string' },
944+
{ type: 'array' },
945+
],
946+
items: {
947+
type: 'string',
948+
},
949+
});
950+
const parseResult = parse(context, schema);
951+
952+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
953+
954+
expect(parseResult).to.contain.warning(
955+
"'Schema Object' has limited support for 'oneOf', use of 'items' with 'oneOf' is not supported"
956+
);
957+
958+
const element = parseResult.get(0).content;
959+
expect(element).to.be.instanceof(namespace.elements.Enum);
960+
961+
expect(element.enumerations.length).to.equal(2);
962+
expect(element.enumerations.get(0)).to.be.instanceof(namespace.elements.String);
963+
expect(element.enumerations.get(1)).to.be.instanceof(namespace.elements.Array);
964+
});
965+
});
825966
});

0 commit comments

Comments
 (0)