Skip to content

Commit dfb2b36

Browse files
kokovtsevraveclassic
authored andcommitted
fix: void response missing from union (#104) (#110)
BREAKING CHANGE: void response is added to resulting response type
1 parent 51c0029 commit dfb2b36

File tree

7 files changed

+132
-17
lines changed

7 files changed

+132
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { serializeOperationResponses } from '../responses-object';
2+
import { constant } from 'fp-ts/lib/function';
3+
import { fromString } from '../../../../../utils/ref';
4+
import { pipe } from 'fp-ts/lib/pipeable';
5+
import { either } from 'fp-ts';
6+
import { sequenceTEither } from '@devexperts/utils/dist/adt/either.utils';
7+
import { ResponsesObject } from '../../../../../schema/2.0/responses-object';
8+
9+
describe('ResponsesObject', () => {
10+
describe('serializeResponsesObject', () => {
11+
it('should serialize void response if it is the only response type', () => {
12+
const responses = pipe(
13+
ResponsesObject.decode({
14+
200: {
15+
description: 'Success (void)',
16+
},
17+
}),
18+
either.mapLeft(constant(new Error())),
19+
);
20+
21+
const result = pipe(
22+
sequenceTEither(fromString('#/test'), responses),
23+
either.chain(([ref, responses]) => serializeOperationResponses(ref, responses)),
24+
);
25+
26+
pipe(
27+
result,
28+
either.fold(fail, result => {
29+
expect(result.type).toEqual('void');
30+
expect(result.io).toEqual('tvoid');
31+
}),
32+
);
33+
});
34+
35+
it('should include void response into the union if needed', () => {
36+
const responses = pipe(
37+
ResponsesObject.decode({
38+
200: {
39+
description: 'Success (void)',
40+
},
41+
400: {
42+
description: 'Error',
43+
schema: {
44+
type: 'object',
45+
required: ['code'],
46+
properties: {
47+
code: {
48+
type: 'number',
49+
},
50+
},
51+
},
52+
},
53+
}),
54+
either.mapLeft(constant(new Error())),
55+
);
56+
57+
const result = pipe(
58+
sequenceTEither(fromString('#/test'), responses),
59+
either.chain(([ref, responses]) => serializeOperationResponses(ref, responses)),
60+
);
61+
62+
pipe(
63+
result,
64+
either.fold(fail, result => {
65+
expect(result.type).toEqual('void|{ code: number }');
66+
expect(result.io).toEqual("union([tvoid,type({ code: number }, 'test')])");
67+
}),
68+
);
69+
});
70+
});
71+
});
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { ResponseObject } from '../../../../schema/2.0/response-object';
2-
import { map, Option } from 'fp-ts/lib/Option';
3-
import { SerializedType } from '../../common/data/serialized-type';
2+
import { SerializedType, SERIALIZED_VOID_TYPE } from '../../common/data/serialized-type';
43
import { pipe } from 'fp-ts/lib/pipeable';
54
import { serializeSchemaObject } from './schema-object';
6-
import { Either } from 'fp-ts/lib/Either';
5+
import { Either, right } from 'fp-ts/lib/Either';
76
import { Ref } from '../../../../utils/ref';
7+
import { option } from 'fp-ts';
88

9-
export const serializeResponseObject = (from: Ref, response: ResponseObject): Option<Either<Error, SerializedType>> =>
9+
export const serializeResponseObject = (from: Ref, response: ResponseObject): Either<Error, SerializedType> =>
1010
pipe(
1111
response.schema,
12-
map(schema => serializeSchemaObject(from, schema)),
12+
option.fold(
13+
() => right(SERIALIZED_VOID_TYPE),
14+
schema => serializeSchemaObject(from, schema),
15+
),
1316
);

src/language/typescript/2.0/serializers/responses-definitions-object.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { getIOName, getTypeName } from '../../common/utils';
22
import { addPathParts, Ref } from '../../../../utils/ref';
33
import { ResponsesDefinitionsObject } from '../../../../schema/2.0/responses-definitions-object';
4-
import { Either, right } from 'fp-ts/lib/Either';
4+
import { Either } from 'fp-ts/lib/Either';
55
import { directory, file, FSEntity } from '../../../../utils/fs';
66
import { pipe } from 'fp-ts/lib/pipeable';
7-
import { either, option, record } from 'fp-ts';
7+
import { either, record } from 'fp-ts';
88
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
99
import { ResponseObject } from '../../../../schema/2.0/response-object';
1010
import { serializeResponseObject } from './response-object';
11-
import { SERIALIZED_VOID_TYPE } from '../../common/data/serialized-type';
1211
import { serializeDependencies } from '../../common/data/serialized-dependency';
1312

1413
export const serializeResponsesDefinitionsObject = (
@@ -31,7 +30,6 @@ export const serializeResponsesDefinitionsObject = (
3130
const serializeResponse = (from: Ref, responseObject: ResponseObject): Either<Error, FSEntity> =>
3231
pipe(
3332
serializeResponseObject(from, responseObject),
34-
option.getOrElse(() => right(SERIALIZED_VOID_TYPE)),
3533
either.map(serialized => {
3634
const dependencies = serializeDependencies(serialized.dependencies);
3735
return file(

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,22 @@ import { pipe } from 'fp-ts/lib/pipeable';
1111
import { serializeResponseObject } from './response-object';
1212
import { serializedDependency } from '../../common/data/serialized-dependency';
1313
import { concatIfL } from '../../../../utils/array';
14-
import { array, either, record } from 'fp-ts';
14+
import { either, record } from 'fp-ts';
1515
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
1616
import { Either } from 'fp-ts/lib/Either';
1717
import { fromString, Ref } from '../../../../utils/ref';
1818
import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
19-
import { some } from 'fp-ts/lib/Option';
2019

2120
export const serializeOperationResponses = (from: Ref, responses: ResponsesObject): Either<Error, SerializedType> =>
2221
pipe(
2322
responses,
2423
record.collect((code, response) => {
2524
if (ReferenceObjectCodec.is(response)) {
26-
return pipe(fromString(response.$ref), either.map(getSerializedRefType(from)), some);
25+
return pipe(fromString(response.$ref), either.map(getSerializedRefType(from)));
2726
} else {
2827
return serializeResponseObject(from, response);
2928
}
3029
}),
31-
array.compact,
3230
sequenceEither,
3331
either.map(responses => {
3432
const serializedResponses = uniqSerializedTypesByTypeAndIO(responses);

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

+45
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,51 @@ import { reportIfFailed } from '../../../../../utils/io-ts';
1818

1919
describe('SchemaObject', () => {
2020
describe('serializeSchemaObject', () => {
21+
it('should properly handle nested allOf / oneOf', () => {
22+
assert(
23+
property($refArbitrary, ref => {
24+
const schema = SchemaObjectCodec.decode({
25+
allOf: [
26+
{
27+
type: 'object',
28+
properties: {
29+
id: { type: 'string' },
30+
},
31+
required: ['id'],
32+
},
33+
{
34+
oneOf: [
35+
{
36+
type: 'object',
37+
properties: {
38+
value: { type: 'string' },
39+
},
40+
required: ['value'],
41+
},
42+
{
43+
type: 'object',
44+
properties: {
45+
error: { type: 'string' },
46+
},
47+
required: ['error'],
48+
},
49+
],
50+
},
51+
],
52+
});
53+
const serialized = pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(ref)));
54+
pipe(
55+
serialized,
56+
either.fold(fail, result => {
57+
expect(result.type).toEqual('{ id: string } & ({ value: string } | { error: string })');
58+
expect(result.io).toEqual(
59+
'intersection([type({ id: string }),union([type({ value: string }),type({ error: string })])])',
60+
);
61+
}),
62+
);
63+
}),
64+
);
65+
});
2166
describe('array', () => {
2267
it('should serialize using getSerializedArrayType', () => {
2368
const schema = record({

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ describe('SerializedType', () => {
151151
types.length === 1
152152
? head(types)
153153
: serializedType(
154-
intercalated.type,
154+
`(${intercalated.type})`,
155155
`union([${intercalated.io}])`,
156156
[...intercalated.dependencies, serializedDependency('union', 'io-ts')],
157157
intercalated.refs,
@@ -168,7 +168,7 @@ describe('SerializedType', () => {
168168
types.length === 1
169169
? head(types)
170170
: serializedType(
171-
intercalated.type,
171+
`${intercalated.type}`,
172172
`intersection([${intercalated.io}])`,
173173
[...intercalated.dependencies, serializedDependency('intersection', 'io-ts')],
174174
intercalated.refs,

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export const getSerializedUnionType = (serialized: NonEmptyArray<SerializedType>
166166
} else {
167167
const intercalated = intercalateSerializedTypes(serializedType(' | ', ',', [], []), serialized);
168168
return serializedType(
169-
intercalated.type,
169+
`(${intercalated.type})`,
170170
`union([${intercalated.io}])`,
171171
[...intercalated.dependencies, serializedDependency('union', 'io-ts')],
172172
intercalated.refs,
@@ -180,7 +180,7 @@ export const getSerializedIntersectionType = (serialized: NonEmptyArray<Serializ
180180
} else {
181181
const intercalated = intercalateSerializedTypes(serializedType(' & ', ',', [], []), serialized);
182182
return serializedType(
183-
intercalated.type,
183+
`${intercalated.type}`,
184184
`intersection([${intercalated.io}])`,
185185
[...intercalated.dependencies, serializedDependency('intersection', 'io-ts')],
186186
intercalated.refs,

0 commit comments

Comments
 (0)