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

Commit d3b2a5a

Browse files
committed
feat(oas3): support null type with other schema types
1 parent 7f8206d commit d3b2a5a

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ function parseType(context) {
192192
ensureTypesAreUnique,
193193

194194
// FIXME support >1 type
195-
R.when(e => e.length > 1, createWarning(namespace, `'${name}' 'type' more than one type is current unsupported`)));
195+
R.unless(
196+
e => e.length === 0 || e.length === 1 || (e.length === 2 && e.contains('null')),
197+
createWarning(namespace, `'${name}' 'type' more than one type is current unsupported`)
198+
));
196199

197200
return R.cond([
198201
[isString, parseStringType],
@@ -213,10 +216,10 @@ function parseType(context) {
213216
}
214217

215218
// Returns whether the given element value matches the provided schema type
216-
const valueMatchesType = (type, value) => {
219+
const valueMatchesType = R.curry((value, type) => {
217220
const expectedElementType = typeToElementNameMap[type];
218221
return value.element === expectedElementType;
219-
};
222+
});
220223

221224
// Returns whether the given element value matches an enumeration of fixed values
222225
const valueMatchesEnumerationValues = (enumeration, value) => {
@@ -240,9 +243,10 @@ function validateValuesMatchSchema(context, schema) {
240243
}
241244

242245
const type = schema.getValue('type');
243-
if (type && !valueMatchesType(type, member.value)) {
246+
if (type && R.none(valueMatchesType(member.value), type)) {
247+
const types = type.map(t => `'${t}'`).join(', ');
244248
return createWarning(namespace,
245-
`'${name}' '${member.key.toValue()}' does not match expected type '${type}'`, member.value);
249+
`'${name}' '${member.key.toValue()}' does not match expected type ${types}`, member.value);
246250
}
247251

248252
return member;
@@ -375,10 +379,11 @@ function parseSchema(context) {
375379
element = constValue;
376380
} else if (enumerations) {
377381
element = enumerations;
382+
} else if (type.length === 1 || (type.length === 2 && type.includes('null'))) {
383+
const findType = R.find(R.complement(R.equals('nullable')));
384+
element = constructStructure(namespace, schema, findType(type));
378385
} else if (type.length > 1) {
379386
throw new Error('Implementation error: unexpected multiple types');
380-
} else if (type.length === 1) {
381-
element = constructStructure(namespace, schema, type[0]);
382387
} else {
383388
element = new namespace.elements.Enum();
384389
element.enumerations = [
@@ -404,8 +409,9 @@ function parseSchema(context) {
404409
element.description = description;
405410
}
406411

412+
// On OAS 3.0, nullable is a keyword, on OAS 3.1, null goes in type
407413
const nullable = schema.getValue('nullable');
408-
if (nullable) {
414+
if (nullable || (type.includes('null') && element.element !== 'null')) {
409415
const typeAttributes = element.attributes.get('typeAttributes') || new namespace.elements.Array();
410416
typeAttributes.push('nullable');
411417
element.attributes.set('typeAttributes', typeAttributes);

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ describe('Schema Object', () => {
201201

202202
const element = parseResult.get(0).content;
203203
expect(element).to.be.instanceof(namespace.elements.Null);
204+
expect(element.attributes.getValue('typeAttributes')).to.be.undefined;
204205
});
205206
});
206207

@@ -292,6 +293,21 @@ describe('Schema Object', () => {
292293
const string = parseResult.get(0).content;
293294
expect(string).to.be.instanceof(namespace.elements.String);
294295
});
296+
297+
it('when type contains a single value with null', () => {
298+
const schema = new namespace.elements.Object({
299+
type: ['string', 'null'],
300+
});
301+
const parseResult = parse(context, schema);
302+
303+
expect(parseResult.length).to.equal(1);
304+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
305+
expect(parseResult).to.not.contain.annotations;
306+
307+
const string = parseResult.get(0).content;
308+
expect(string).to.be.instanceof(namespace.elements.String);
309+
expect(string.attributes.getValue('typeAttributes')).to.deep.equal(['nullable']);
310+
});
295311
});
296312
});
297313

@@ -860,6 +876,21 @@ describe('Schema Object', () => {
860876
expect(element.attributes.get('default').toValue()).to.equal(null);
861877
});
862878

879+
it('allows a null default value with null type on OpenAPI 3.1', () => {
880+
context.openapiVersion = { major: 3, minor: 1 };
881+
const schema = new namespace.elements.Object({
882+
type: ['string', 'null'],
883+
default: null,
884+
});
885+
const parseResult = parse(context, schema);
886+
887+
expect(parseResult.length).to.equal(1);
888+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.DataStructure);
889+
890+
const element = parseResult.get(0).content;
891+
expect(element.attributes.get('default').toValue()).to.equal(null);
892+
});
893+
863894
it('allows a null default value when nullable is enabled with enum', () => {
864895
const schema = new namespace.elements.Object({
865896
enum: ['my default'],
@@ -888,6 +919,20 @@ describe('Schema Object', () => {
888919
);
889920
});
890921

922+
it('warns when default does not match expected types on OpenAPI 3.1', () => {
923+
context.openapiVersion = { major: 3, minor: 1 };
924+
const schema = new namespace.elements.Object({
925+
type: ['number', 'null'],
926+
default: 'my default',
927+
});
928+
const parseResult = parse(context, schema);
929+
930+
expect(parseResult.length).to.equal(2);
931+
expect(parseResult).to.contain.warning(
932+
"'Schema Object' 'default' does not match expected type 'number', 'null'"
933+
);
934+
});
935+
891936
it('warns when default does not match enumeration value', () => {
892937
const schema = new namespace.elements.Object({
893938
type: 'string',

0 commit comments

Comments
 (0)