Skip to content

Commit 77c4361

Browse files
authored
Merge pull request #146 from mankdev/nea-support
Support nonEmptyArray for arrays with minItems > 0 for OpenAPI 3.0
2 parents 64b2b83 + 5038b65 commit 77c4361

File tree

9 files changed

+95
-19
lines changed

9 files changed

+95
-19
lines changed

src/language/typescript/2.0/serializers/items-object.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import { Ref } from '../../../../utils/ref';
1111
import { pipe } from 'fp-ts/lib/pipeable';
1212
import { either } from 'fp-ts';
1313
import { Either, right } from 'fp-ts/lib/Either';
14+
import { none } from 'fp-ts/lib/Option';
1415

1516
export const serializeItemsObject = (from: Ref, itemsObject: ItemsObject): Either<Error, SerializedType> => {
1617
switch (itemsObject.type) {
1718
case 'array': {
18-
return pipe(serializeItemsObject(from, itemsObject.items), either.map(getSerializedArrayType()));
19+
return pipe(serializeItemsObject(from, itemsObject.items), either.map(getSerializedArrayType(none)));
1920
}
2021
case 'string': {
2122
return getSerializedStringType(from, itemsObject.format);

src/language/typescript/2.0/serializers/parameter-object.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { fromSerializedType, SerializedParameter } from '../../common/data/seria
1515
import { pipe } from 'fp-ts/lib/pipeable';
1616
import { either, option } from 'fp-ts';
1717
import { constFalse } from 'fp-ts/lib/function';
18+
import { none } from 'fp-ts/lib/Option';
1819

1920
export const serializeParameterObject = (
2021
from: Ref,
@@ -45,7 +46,7 @@ export const serializeParameterObject = (
4546
case 'array': {
4647
return pipe(
4748
serializeItemsObject(from, parameterObject.items),
48-
either.map(getSerializedArrayType()),
49+
either.map(getSerializedArrayType(none)),
4950
either.map(toSerializedParameter),
5051
);
5152
}

src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,21 @@ describe('SchemaObject', () => {
7272
format: constant(none),
7373
deprecated: constant(none),
7474
nullable: constant(none),
75+
minItems: constant(none),
76+
maxItems: constant(none),
7577
}),
7678
format: constant(none),
7779
deprecated: constant(none),
7880
nullable: constant(none),
81+
minItems: constant(none),
82+
maxItems: constant(none),
7983
});
8084
assert(
8185
property($refArbitrary, schema, string(), (from, schema, name) => {
8286
const expected = pipe(
8387
schema.items,
8488
serializeSchemaObject(from, name),
85-
either.map(getSerializedArrayType(name)),
89+
either.map(getSerializedArrayType(none, name)),
8690
);
8791
const serialized = pipe(schema, serializeSchemaObject(from, name));
8892
expect(serialized).toEqual(expected);
@@ -98,7 +102,11 @@ describe('SchemaObject', () => {
98102
$ref: $refArbitrary.$ref,
99103
},
100104
});
101-
const expected = pipe($refArbitrary, getSerializedRefType(from), getSerializedArrayType(name));
105+
const expected = pipe(
106+
$refArbitrary,
107+
getSerializedRefType(from),
108+
getSerializedArrayType(none, name),
109+
);
102110
expect(pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(from, name)))).toEqual(
103111
right(expected),
104112
);
@@ -125,7 +133,7 @@ describe('SchemaObject', () => {
125133
const expected = pipe(
126134
ref,
127135
getSerializedRefType(ref),
128-
getSerializedArrayType(undefined),
136+
getSerializedArrayType(none, undefined),
129137
getSerializedOptionPropertyType('children', true),
130138
getSerializedObjectType(undefined),
131139
getSerializedRecursiveType(ref, true),

src/language/typescript/3.0/serializers/schema-object.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,14 @@ const serializeSchemaObjectWithRecursion = (from: Ref, shouldTrackRecursion: boo
8787
const serialized = ReferenceObjectCodec.is(items)
8888
? pipe(
8989
fromString(items.$ref),
90-
mapLeft(() => new Error(`Unable to serialize SchemaObjeft array items ref "${items.$ref}"`)),
90+
mapLeft(() => new Error(`Unable to serialize SchemaObject array items ref "${items.$ref}"`)),
9191
either.map(getSerializedRefType(from)),
9292
)
9393
: pipe(items, serializeSchemaObjectWithRecursion(from, false, undefined));
94+
9495
return pipe(
9596
serialized,
96-
either.map(getSerializedArrayType(name)),
97+
either.map(getSerializedArrayType(schemaObject.minItems, name)),
9798
either.map(getSerializedNullableType(isNullable)),
9899
);
99100
}
@@ -109,7 +110,7 @@ const serializeSchemaObjectWithRecursion = (from: Ref, shouldTrackRecursion: boo
109110
mapLeft(
110111
() =>
111112
new Error(
112-
`Unablew to serialize SchemaObject additionalProperties ref "${additionalProperties.$ref}"`,
113+
`Unable to serialize SchemaObject additionalProperties ref "${additionalProperties.$ref}"`,
113114
),
114115
),
115116
either.map(getSerializedRefType(from)),

src/language/typescript/asyncapi-2.0.0/serializers/schema-object.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { ReferenceObject, ReferenceObjectCodec } from '../../../../schema/asynca
3636
import { traverseNEAEither } from '../../../../utils/either';
3737
import { constFalse } from 'fp-ts/lib/function';
3838
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
39-
import { Option } from 'fp-ts/lib/Option';
39+
import { none, Option } from 'fp-ts/lib/Option';
4040

4141
export const serializeSchemaObject = (
4242
from: Ref,
@@ -192,5 +192,5 @@ const serializeArray = (
192192
const serialized = ReferenceObjectCodec.is(items)
193193
? pipe(fromString(items.$ref), either.map(getSerializedRefType(from)))
194194
: serializeSchemaObjectWithRecursion(from, items, false);
195-
return pipe(serialized, either.map(getSerializedArrayType(name)));
195+
return pipe(serialized, either.map(getSerializedArrayType(none, name)));
196196
};

src/language/typescript/common/data/__tests__/serialized-type.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('SerializedType', () => {
3535
it('getSerializedArrayType', () => {
3636
assert(
3737
property(serializedTypeArbitrary, name, (s, name) => {
38-
expect(pipe(s, getSerializedArrayType(name))).toEqual(
38+
expect(pipe(s, getSerializedArrayType(none, name))).toEqual(
3939
serializedType(
4040
`Array<${s.type}>`,
4141
`array(${s.io}${when(name !== undefined, `, '${name}'`)})`,
@@ -46,6 +46,24 @@ describe('SerializedType', () => {
4646
}),
4747
);
4848
});
49+
it('getSerializedArrayType with minItems not 0', () => {
50+
assert(
51+
property(serializedTypeArbitrary, name, (s, name) => {
52+
expect(pipe(s, getSerializedArrayType(some(1), name))).toEqual(
53+
serializedType(
54+
`NonEmptyArray<${s.type}>`,
55+
`nonEmptyArray(${s.io}${when(name !== undefined, `, '${name}'`)})`,
56+
[
57+
...s.dependencies,
58+
serializedDependency('nonEmptyArray', 'io-ts-types/lib/nonEmptyArray'),
59+
serializedDependency('NonEmptyArray', 'fp-ts/lib/NonEmptyArray'),
60+
],
61+
s.refs,
62+
),
63+
);
64+
}),
65+
);
66+
});
4967
it('getSerializedPropertyType', () => {
5068
assert(
5169
property(string(), serializedTypeArbitrary, boolean(), (name, s, isRequired) => {

src/language/typescript/common/data/serialized-type.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { head, NonEmptyArray } from 'fp-ts/lib/NonEmptyArray';
1818
import { JSONPrimitive } from '../../../../utils/io-ts';
1919
import { pipe } from 'fp-ts/lib/pipeable';
2020
import { nonEmptyArray, option } from 'fp-ts';
21-
import { none, Option, some } from 'fp-ts/lib/Option';
21+
import { none, Option, some, exists } from 'fp-ts/lib/Option';
2222
import { utilsRef } from '../bundled/utils';
2323
import { Either } from 'fp-ts/lib/Either';
2424
import { combineEither } from '@devexperts/utils/dist/adt/either.utils';
@@ -123,12 +123,30 @@ export const SERIALIZED_NULL_TYPE = serializedType('null', 'nullType', [serializ
123123
export const getSerializedNullableType = (isNullable: boolean) => (type: SerializedType): SerializedType =>
124124
isNullable ? getSerializedUnionType([type, SERIALIZED_NULL_TYPE]) : type;
125125

126-
export const getSerializedArrayType = (name?: string) => (serialized: SerializedType): SerializedType =>
127-
serializedType(
128-
`Array<${serialized.type}>`,
129-
`array(${serialized.io}${when(name !== undefined, `, '${name}'`)})`,
130-
[...serialized.dependencies, serializedDependency('array', 'io-ts')],
131-
serialized.refs,
126+
export const getSerializedArrayType = (minItems: Option<number>, name?: string) => (
127+
serialized: SerializedType,
128+
): SerializedType =>
129+
pipe(
130+
minItems,
131+
exists(minItems => minItems > 0),
132+
isNonEmpty =>
133+
isNonEmpty
134+
? serializedType(
135+
`NonEmptyArray<${serialized.type}>`,
136+
`nonEmptyArray(${serialized.io}${when(name !== undefined, `, '${name}'`)})`,
137+
[
138+
...serialized.dependencies,
139+
serializedDependency('nonEmptyArray', 'io-ts-types/lib/nonEmptyArray'),
140+
serializedDependency('NonEmptyArray', 'fp-ts/lib/NonEmptyArray'),
141+
],
142+
serialized.refs,
143+
)
144+
: serializedType(
145+
`Array<${serialized.type}>`,
146+
`array(${serialized.io}${when(name !== undefined, `, '${name}'`)})`,
147+
[...serialized.dependencies, serializedDependency('array', 'io-ts')],
148+
serialized.refs,
149+
),
132150
);
133151

134152
export const getSerializedRefType = (from: Ref) => (to: Ref): SerializedType => {

src/schema/3.0/schema-object.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@ import { array, boolean, intersection, literal, record, recursion, string, type,
22
import { ReferenceObject, ReferenceObjectCodec } from './reference-object';
33
import { Option } from 'fp-ts/lib/Option';
44
import { optionFromNullable } from 'io-ts-types/lib/optionFromNullable';
5-
import { Codec, JSONPrimitive, JSONPrimitiveCodec } from '../../utils/io-ts';
5+
import { Codec, JSONPrimitive, JSONPrimitiveCodec, numberOption } from '../../utils/io-ts';
66
import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray';
77
import { nonEmptyArray } from 'io-ts-types/lib/nonEmptyArray';
88

99
export interface BaseSchemaObject {
1010
readonly format: Option<string>;
1111
readonly deprecated: Option<boolean>;
1212
readonly nullable: Option<boolean>;
13+
readonly maxItems: Option<number>;
14+
readonly minItems: Option<number>;
1315
}
1416

1517
const BaseSchemaObjectCodec: Codec<BaseSchemaObject> = type({
1618
format: optionFromNullable(string),
1719
deprecated: optionFromNullable(boolean),
1820
nullable: optionFromNullable(boolean),
21+
maxItems: numberOption,
22+
minItems: numberOption,
1923
});
2024

2125
export interface EnumSchemaObject extends BaseSchemaObject {

test/specs/3.0/demo.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,31 @@ components:
135135
properties:
136136
self:
137137
$ref: '#/components/schemas/37sds afd,asd.324sfa as2_+='
138+
TestNonEmpty:
139+
type: array
140+
minItems: 1
141+
items:
142+
type: object
143+
properties:
144+
foo:
145+
type: string
146+
bar:
147+
type: number
148+
TestArrayWithMinItems0:
149+
type: array
150+
minItems: 0
151+
items:
152+
type: object
153+
properties:
154+
foo:
155+
type: string
156+
bar:
157+
type: number
158+
TestNonEmptyRef:
159+
type: array
160+
minItems: 1
161+
items:
162+
$ref: "#/components/schemas/TestAllOf"
138163
TestNullable:
139164
type: array
140165
nullable: true

0 commit comments

Comments
 (0)