diff --git a/src/language/typescript/2.0/serializers/items-object.ts b/src/language/typescript/2.0/serializers/items-object.ts index e68859a..6524ae1 100644 --- a/src/language/typescript/2.0/serializers/items-object.ts +++ b/src/language/typescript/2.0/serializers/items-object.ts @@ -11,11 +11,12 @@ import { Ref } from '../../../../utils/ref'; import { pipe } from 'fp-ts/lib/pipeable'; import { either } from 'fp-ts'; import { Either, right } from 'fp-ts/lib/Either'; +import { none } from 'fp-ts/lib/Option'; export const serializeItemsObject = (from: Ref, itemsObject: ItemsObject): Either => { switch (itemsObject.type) { case 'array': { - return pipe(serializeItemsObject(from, itemsObject.items), either.map(getSerializedArrayType())); + return pipe(serializeItemsObject(from, itemsObject.items), either.map(getSerializedArrayType(none))); } case 'string': { return getSerializedStringType(from, itemsObject.format); diff --git a/src/language/typescript/2.0/serializers/parameter-object.ts b/src/language/typescript/2.0/serializers/parameter-object.ts index 49e807e..a4be6e1 100644 --- a/src/language/typescript/2.0/serializers/parameter-object.ts +++ b/src/language/typescript/2.0/serializers/parameter-object.ts @@ -15,6 +15,7 @@ import { fromSerializedType, SerializedParameter } from '../../common/data/seria import { pipe } from 'fp-ts/lib/pipeable'; import { either, option } from 'fp-ts'; import { constFalse } from 'fp-ts/lib/function'; +import { none } from 'fp-ts/lib/Option'; export const serializeParameterObject = ( from: Ref, @@ -45,7 +46,7 @@ export const serializeParameterObject = ( case 'array': { return pipe( serializeItemsObject(from, parameterObject.items), - either.map(getSerializedArrayType()), + either.map(getSerializedArrayType(none)), either.map(toSerializedParameter), ); } diff --git a/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts b/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts index c1e6d7e..53f5832 100644 --- a/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts +++ b/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts @@ -72,17 +72,21 @@ describe('SchemaObject', () => { format: constant(none), deprecated: constant(none), nullable: constant(none), + minItems: constant(none), + maxItems: constant(none), }), format: constant(none), deprecated: constant(none), nullable: constant(none), + minItems: constant(none), + maxItems: constant(none), }); assert( property($refArbitrary, schema, string(), (from, schema, name) => { const expected = pipe( schema.items, serializeSchemaObject(from, name), - either.map(getSerializedArrayType(name)), + either.map(getSerializedArrayType(none, name)), ); const serialized = pipe(schema, serializeSchemaObject(from, name)); expect(serialized).toEqual(expected); @@ -98,7 +102,11 @@ describe('SchemaObject', () => { $ref: $refArbitrary.$ref, }, }); - const expected = pipe($refArbitrary, getSerializedRefType(from), getSerializedArrayType(name)); + const expected = pipe( + $refArbitrary, + getSerializedRefType(from), + getSerializedArrayType(none, name), + ); expect(pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(from, name)))).toEqual( right(expected), ); @@ -125,7 +133,7 @@ describe('SchemaObject', () => { const expected = pipe( ref, getSerializedRefType(ref), - getSerializedArrayType(undefined), + getSerializedArrayType(none, undefined), getSerializedOptionPropertyType('children', true), getSerializedObjectType(undefined), getSerializedRecursiveType(ref, true), diff --git a/src/language/typescript/3.0/serializers/schema-object.ts b/src/language/typescript/3.0/serializers/schema-object.ts index e5c2539..af25a2f 100644 --- a/src/language/typescript/3.0/serializers/schema-object.ts +++ b/src/language/typescript/3.0/serializers/schema-object.ts @@ -87,13 +87,14 @@ const serializeSchemaObjectWithRecursion = (from: Ref, shouldTrackRecursion: boo const serialized = ReferenceObjectCodec.is(items) ? pipe( fromString(items.$ref), - mapLeft(() => new Error(`Unable to serialize SchemaObjeft array items ref "${items.$ref}"`)), + mapLeft(() => new Error(`Unable to serialize SchemaObject array items ref "${items.$ref}"`)), either.map(getSerializedRefType(from)), ) : pipe(items, serializeSchemaObjectWithRecursion(from, false, undefined)); + return pipe( serialized, - either.map(getSerializedArrayType(name)), + either.map(getSerializedArrayType(schemaObject.minItems, name)), either.map(getSerializedNullableType(isNullable)), ); } @@ -109,7 +110,7 @@ const serializeSchemaObjectWithRecursion = (from: Ref, shouldTrackRecursion: boo mapLeft( () => new Error( - `Unablew to serialize SchemaObject additionalProperties ref "${additionalProperties.$ref}"`, + `Unable to serialize SchemaObject additionalProperties ref "${additionalProperties.$ref}"`, ), ), either.map(getSerializedRefType(from)), diff --git a/src/language/typescript/asyncapi-2.0.0/serializers/schema-object.ts b/src/language/typescript/asyncapi-2.0.0/serializers/schema-object.ts index e260285..c0b64c8 100644 --- a/src/language/typescript/asyncapi-2.0.0/serializers/schema-object.ts +++ b/src/language/typescript/asyncapi-2.0.0/serializers/schema-object.ts @@ -36,7 +36,7 @@ import { ReferenceObject, ReferenceObjectCodec } from '../../../../schema/asynca import { traverseNEAEither } from '../../../../utils/either'; import { constFalse } from 'fp-ts/lib/function'; import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils'; -import { Option } from 'fp-ts/lib/Option'; +import { none, Option } from 'fp-ts/lib/Option'; export const serializeSchemaObject = ( from: Ref, @@ -192,5 +192,5 @@ const serializeArray = ( const serialized = ReferenceObjectCodec.is(items) ? pipe(fromString(items.$ref), either.map(getSerializedRefType(from))) : serializeSchemaObjectWithRecursion(from, items, false); - return pipe(serialized, either.map(getSerializedArrayType(name))); + return pipe(serialized, either.map(getSerializedArrayType(none, name))); }; diff --git a/src/language/typescript/common/data/__tests__/serialized-type.spec.ts b/src/language/typescript/common/data/__tests__/serialized-type.spec.ts index 7ec3cf3..a027e4b 100644 --- a/src/language/typescript/common/data/__tests__/serialized-type.spec.ts +++ b/src/language/typescript/common/data/__tests__/serialized-type.spec.ts @@ -35,7 +35,7 @@ describe('SerializedType', () => { it('getSerializedArrayType', () => { assert( property(serializedTypeArbitrary, name, (s, name) => { - expect(pipe(s, getSerializedArrayType(name))).toEqual( + expect(pipe(s, getSerializedArrayType(none, name))).toEqual( serializedType( `Array<${s.type}>`, `array(${s.io}${when(name !== undefined, `, '${name}'`)})`, @@ -46,6 +46,24 @@ describe('SerializedType', () => { }), ); }); + it('getSerializedArrayType with minItems not 0', () => { + assert( + property(serializedTypeArbitrary, name, (s, name) => { + expect(pipe(s, getSerializedArrayType(some(1), name))).toEqual( + serializedType( + `NonEmptyArray<${s.type}>`, + `nonEmptyArray(${s.io}${when(name !== undefined, `, '${name}'`)})`, + [ + ...s.dependencies, + serializedDependency('nonEmptyArray', 'io-ts-types/lib/nonEmptyArray'), + serializedDependency('NonEmptyArray', 'fp-ts/lib/NonEmptyArray'), + ], + s.refs, + ), + ); + }), + ); + }); it('getSerializedPropertyType', () => { assert( property(string(), serializedTypeArbitrary, boolean(), (name, s, isRequired) => { diff --git a/src/language/typescript/common/data/serialized-type.ts b/src/language/typescript/common/data/serialized-type.ts index 9725ce6..89660bf 100644 --- a/src/language/typescript/common/data/serialized-type.ts +++ b/src/language/typescript/common/data/serialized-type.ts @@ -18,7 +18,7 @@ import { head, NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'; import { JSONPrimitive } from '../../../../utils/io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { nonEmptyArray, option } from 'fp-ts'; -import { none, Option, some } from 'fp-ts/lib/Option'; +import { none, Option, some, exists } from 'fp-ts/lib/Option'; import { utilsRef } from '../bundled/utils'; import { Either } from 'fp-ts/lib/Either'; import { combineEither } from '@devexperts/utils/dist/adt/either.utils'; @@ -123,12 +123,30 @@ export const SERIALIZED_NULL_TYPE = serializedType('null', 'nullType', [serializ export const getSerializedNullableType = (isNullable: boolean) => (type: SerializedType): SerializedType => isNullable ? getSerializedUnionType([type, SERIALIZED_NULL_TYPE]) : type; -export const getSerializedArrayType = (name?: string) => (serialized: SerializedType): SerializedType => - serializedType( - `Array<${serialized.type}>`, - `array(${serialized.io}${when(name !== undefined, `, '${name}'`)})`, - [...serialized.dependencies, serializedDependency('array', 'io-ts')], - serialized.refs, +export const getSerializedArrayType = (minItems: Option, name?: string) => ( + serialized: SerializedType, +): SerializedType => + pipe( + minItems, + exists(minItems => minItems > 0), + isNonEmpty => + isNonEmpty + ? serializedType( + `NonEmptyArray<${serialized.type}>`, + `nonEmptyArray(${serialized.io}${when(name !== undefined, `, '${name}'`)})`, + [ + ...serialized.dependencies, + serializedDependency('nonEmptyArray', 'io-ts-types/lib/nonEmptyArray'), + serializedDependency('NonEmptyArray', 'fp-ts/lib/NonEmptyArray'), + ], + serialized.refs, + ) + : serializedType( + `Array<${serialized.type}>`, + `array(${serialized.io}${when(name !== undefined, `, '${name}'`)})`, + [...serialized.dependencies, serializedDependency('array', 'io-ts')], + serialized.refs, + ), ); export const getSerializedRefType = (from: Ref) => (to: Ref): SerializedType => { diff --git a/src/schema/3.0/schema-object.ts b/src/schema/3.0/schema-object.ts index 0cc35ff..3c89664 100644 --- a/src/schema/3.0/schema-object.ts +++ b/src/schema/3.0/schema-object.ts @@ -2,7 +2,7 @@ import { array, boolean, intersection, literal, record, recursion, string, type, import { ReferenceObject, ReferenceObjectCodec } from './reference-object'; import { Option } from 'fp-ts/lib/Option'; import { optionFromNullable } from 'io-ts-types/lib/optionFromNullable'; -import { Codec, JSONPrimitive, JSONPrimitiveCodec } from '../../utils/io-ts'; +import { Codec, JSONPrimitive, JSONPrimitiveCodec, numberOption } from '../../utils/io-ts'; import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'; import { nonEmptyArray } from 'io-ts-types/lib/nonEmptyArray'; @@ -10,12 +10,16 @@ export interface BaseSchemaObject { readonly format: Option; readonly deprecated: Option; readonly nullable: Option; + readonly maxItems: Option; + readonly minItems: Option; } const BaseSchemaObjectCodec: Codec = type({ format: optionFromNullable(string), deprecated: optionFromNullable(boolean), nullable: optionFromNullable(boolean), + maxItems: numberOption, + minItems: numberOption, }); export interface EnumSchemaObject extends BaseSchemaObject { diff --git a/test/specs/3.0/demo.yml b/test/specs/3.0/demo.yml index 262a79d..10bc241 100644 --- a/test/specs/3.0/demo.yml +++ b/test/specs/3.0/demo.yml @@ -135,6 +135,31 @@ components: properties: self: $ref: '#/components/schemas/37sds afd,asd.324sfa as2_+=' + TestNonEmpty: + type: array + minItems: 1 + items: + type: object + properties: + foo: + type: string + bar: + type: number + TestArrayWithMinItems0: + type: array + minItems: 0 + items: + type: object + properties: + foo: + type: string + bar: + type: number + TestNonEmptyRef: + type: array + minItems: 1 + items: + $ref: "#/components/schemas/TestAllOf" TestNullable: type: array nullable: true