Skip to content

Commit ff6a181

Browse files
committed
fix: fix recursive allOf
1 parent 8ab9fc0 commit ff6a181

File tree

10 files changed

+275
-87
lines changed

10 files changed

+275
-87
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { assert, property } from 'fast-check';
22
import { $refArbitrary } from '../../../../../utils/__tests__/ref.spec';
33
import {
4+
getSerializedIntersectionType,
45
getSerializedObjectType,
56
getSerializedPropertyType,
67
getSerializedRecursiveType,
78
getSerializedRefType,
9+
SERIALIZED_STRING_TYPE,
810
} from '../../../common/data/serialized-type';
911
import { pipe } from 'fp-ts/lib/pipeable';
1012
import { serializeSchemaObject } from '../schema-object';
@@ -15,69 +17,153 @@ import { reportIfFailed } from '../../../../../utils/io-ts';
1517

1618
describe('SchemaObject serializer', () => {
1719
describe('recursive', () => {
18-
it('level 1', () => {
19-
assert(
20-
property($refArbitrary, ref => {
21-
const schema = SchemaObjectCodec.decode({
22-
type: 'object',
23-
required: ['recursive'],
24-
properties: {
25-
recursive: {
26-
$ref: ref.$ref,
20+
describe('properties', () => {
21+
it('level 1', () => {
22+
assert(
23+
property($refArbitrary, ref => {
24+
const schema = SchemaObjectCodec.decode({
25+
type: 'object',
26+
required: ['recursive'],
27+
properties: {
28+
recursive: {
29+
$ref: ref.$ref,
30+
},
2731
},
28-
},
29-
});
30-
const expected = pipe(
31-
ref,
32-
getSerializedRefType(ref),
33-
getSerializedPropertyType('recursive', true),
34-
getSerializedObjectType(),
35-
getSerializedRecursiveType(ref, true),
36-
);
37-
const serialized = pipe(
38-
schema,
39-
reportIfFailed,
40-
either.chain(schema => serializeSchemaObject(ref, schema)),
41-
);
42-
expect(serialized).toEqual(right(expected));
43-
}),
44-
);
45-
});
46-
it('level 2', () => {
47-
assert(
48-
property($refArbitrary, ref => {
49-
const schema = SchemaObjectCodec.decode({
50-
type: 'object',
51-
required: ['children'],
52-
properties: {
53-
children: {
54-
type: 'object',
55-
required: ['recursive'],
56-
properties: {
57-
recursive: {
58-
$ref: ref.$ref,
32+
});
33+
const expected = pipe(
34+
ref,
35+
getSerializedRefType(ref),
36+
getSerializedPropertyType('recursive', true),
37+
getSerializedObjectType(),
38+
getSerializedRecursiveType(ref, true),
39+
);
40+
const serialized = pipe(
41+
schema,
42+
reportIfFailed,
43+
either.chain(schema => serializeSchemaObject(ref, schema)),
44+
);
45+
expect(serialized).toEqual(right(expected));
46+
}),
47+
);
48+
});
49+
it('level 2', () => {
50+
assert(
51+
property($refArbitrary, ref => {
52+
const schema = SchemaObjectCodec.decode({
53+
type: 'object',
54+
required: ['children'],
55+
properties: {
56+
children: {
57+
type: 'object',
58+
required: ['recursive'],
59+
properties: {
60+
recursive: {
61+
$ref: ref.$ref,
62+
},
5963
},
6064
},
6165
},
62-
},
63-
});
64-
const expected = pipe(
65-
ref,
66-
getSerializedRefType(ref),
67-
getSerializedPropertyType('recursive', true),
68-
getSerializedObjectType(),
69-
getSerializedPropertyType('children', true),
70-
getSerializedObjectType(),
71-
getSerializedRecursiveType(ref, true),
72-
);
73-
const serialized = pipe(
74-
schema,
75-
reportIfFailed,
76-
either.chain(schema => serializeSchemaObject(ref, schema)),
77-
);
78-
expect(serialized).toEqual(right(expected));
79-
}),
80-
);
66+
});
67+
const expected = pipe(
68+
ref,
69+
getSerializedRefType(ref),
70+
getSerializedPropertyType('recursive', true),
71+
getSerializedObjectType(),
72+
getSerializedPropertyType('children', true),
73+
getSerializedObjectType(),
74+
getSerializedRecursiveType(ref, true),
75+
);
76+
const serialized = pipe(
77+
schema,
78+
reportIfFailed,
79+
either.chain(schema => serializeSchemaObject(ref, schema)),
80+
);
81+
expect(serialized).toEqual(right(expected));
82+
}),
83+
);
84+
});
85+
});
86+
describe('allOf', () => {
87+
it('level 1', () => {
88+
assert(
89+
property($refArbitrary, ref => {
90+
const schema = SchemaObjectCodec.decode({
91+
allOf: [
92+
{
93+
type: 'string',
94+
},
95+
{
96+
type: 'object',
97+
required: ['self'],
98+
properties: {
99+
self: {
100+
$ref: ref.$ref,
101+
},
102+
},
103+
},
104+
],
105+
});
106+
const serialized = pipe(
107+
schema,
108+
reportIfFailed,
109+
either.chain(schema => serializeSchemaObject(ref, schema)),
110+
);
111+
const expected = pipe(
112+
ref,
113+
getSerializedRefType(ref),
114+
getSerializedPropertyType('self', true),
115+
getSerializedObjectType(),
116+
serialized => getSerializedIntersectionType([SERIALIZED_STRING_TYPE, serialized]),
117+
getSerializedRecursiveType(ref, true),
118+
);
119+
expect(serialized).toEqual(right(expected));
120+
}),
121+
);
122+
});
123+
it('level 2', () => {
124+
assert(
125+
property($refArbitrary, ref => {
126+
const schema = SchemaObjectCodec.decode({
127+
allOf: [
128+
{
129+
type: 'string',
130+
},
131+
{
132+
type: 'object',
133+
required: ['nested'],
134+
properties: {
135+
nested: {
136+
type: 'object',
137+
required: ['self'],
138+
properties: {
139+
self: {
140+
$ref: ref.$ref,
141+
},
142+
},
143+
},
144+
},
145+
},
146+
],
147+
});
148+
const serialized = pipe(
149+
schema,
150+
reportIfFailed,
151+
either.chain(schema => serializeSchemaObject(ref, schema)),
152+
);
153+
const expected = pipe(
154+
ref,
155+
getSerializedRefType(ref),
156+
getSerializedPropertyType('self', true),
157+
getSerializedObjectType(),
158+
getSerializedPropertyType('nested', true),
159+
getSerializedObjectType(),
160+
serialized => getSerializedIntersectionType([SERIALIZED_STRING_TYPE, serialized]),
161+
getSerializedRecursiveType(ref, true),
162+
);
163+
expect(serialized).toEqual(right(expected));
164+
}),
165+
);
166+
});
81167
});
82168
});
83169
});

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

+7-19
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,19 @@ import {
1212
getSerializedObjectType,
1313
getSerializedRecursiveType,
1414
getSerializedDictionaryType,
15+
getSerializedIntersectionType,
1516
} from '../../common/data/serialized-type';
16-
import { monoidDependencies, serializedDependency } from '../../common/data/serialized-dependency';
17+
import { serializedDependency } from '../../common/data/serialized-dependency';
1718
import { AllOfSchemaObject, SchemaObject } from '../../../../schema/2.0/schema-object/schema-object';
1819
import { none, some } from 'fp-ts/lib/Option';
1920
import { pipe } from 'fp-ts/lib/pipeable';
20-
import { fold, monoidString } from 'fp-ts/lib/Monoid';
21-
import { intercalate } from 'fp-ts/lib/Foldable';
2221
import { constFalse } from 'fp-ts/lib/function';
2322
import { includes } from '../../../../utils/array';
2423
import { fromString, Ref } from '../../../../utils/ref';
2524
import { Either, right } from 'fp-ts/lib/Either';
2625
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
27-
import { array, either, option, record } from 'fp-ts';
28-
import { traverseArrayEither } from '../../../../utils/either';
26+
import { either, option, record } from 'fp-ts';
27+
import { traverseNEAEither } from '../../../../utils/either';
2928
import { ReferenceObject } from '../../../../schema/2.0/reference-object';
3029

3130
export const serializeSchemaObject = (from: Ref, schema: SchemaObject): Either<Error, SerializedType> =>
@@ -46,20 +45,9 @@ const serializeSchemaObjectWithRecursion = (
4645

4746
if (AllOfSchemaObject.is(schema)) {
4847
return pipe(
49-
traverseArrayEither(schema.allOf, item => serializeSchemaObject(from, item)),
50-
either.map(results => {
51-
const types = results.map(item => item.type);
52-
const ios = results.map(item => item.io);
53-
const dependencies = fold(monoidDependencies)(results.map(item => item.dependencies));
54-
const refs = fold(array.getMonoid<Ref>())(results.map(item => item.refs));
55-
56-
return serializedType(
57-
intercalate(monoidString, array.array)(' & ', types),
58-
`intersection([${intercalate(monoidString, array.array)(', ', ios)}])`,
59-
[serializedDependency('intersection', 'io-ts'), ...dependencies],
60-
refs,
61-
);
62-
}),
48+
traverseNEAEither(schema.allOf, item => serializeSchemaObjectWithRecursion(from, item, false)),
49+
either.map(getSerializedIntersectionType),
50+
either.map(getSerializedRecursiveType(from, shouldTrackRecursion)),
6351
);
6452
}
6553

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
getSerializedArrayType,
33
getSerializedDictionaryType,
4+
getSerializedIntersectionType,
45
getSerializedObjectType,
56
getSerializedPropertyType,
67
getSerializedRecursiveType,
@@ -21,8 +22,9 @@ import { constFalse } from 'fp-ts/lib/function';
2122
import { includes } from '../../../../utils/array';
2223
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
2324
import { fromString, Ref } from '../../../../utils/ref';
24-
import { SchemaObject } from '../../../../schema/3.0/schema-object';
25+
import { AllOfSchemaObjectCodec, SchemaObject } from '../../../../schema/3.0/schema-object';
2526
import { ReferenceObject, ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
27+
import { traverseNEAEither } from '../../../../utils/either';
2628

2729
type AdditionalProperties = boolean | ReferenceObject | SchemaObject;
2830
type AllowedAdditionalProperties = true | ReferenceObject | SchemaObject;
@@ -39,6 +41,23 @@ export const serializeSchemaObject = (
3941
const serializeSchemaObjectWithRecursion = (from: Ref, shouldTrackRecursion: boolean, name?: string) => (
4042
schemaObject: SchemaObject,
4143
): Either<Error, SerializedType> => {
44+
if (AllOfSchemaObjectCodec.is(schemaObject)) {
45+
return pipe(
46+
traverseNEAEither(schemaObject.allOf, item => {
47+
if (ReferenceObjectCodec.is(item)) {
48+
return pipe(
49+
fromString(item.$ref),
50+
either.map(getSerializedRefType(from)),
51+
);
52+
} else {
53+
return serializeSchemaObjectWithRecursion(from, false)(item);
54+
}
55+
}),
56+
either.map(getSerializedIntersectionType),
57+
either.map(getSerializedRecursiveType(from, shouldTrackRecursion)),
58+
);
59+
}
60+
4261
switch (schemaObject.type) {
4362
case 'string': {
4463
return right(SERIALIZED_STRING_TYPE);

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

+39-1
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ import { array, assert, boolean, constant, oneof, property, string, tuple } from
22
import {
33
getSerializedArrayType,
44
getSerializedDictionaryType,
5+
getSerializedIntersectionType,
56
getSerializedObjectType,
67
getSerializedPropertyType,
78
getSerializedRefType,
9+
getSerializedUnionType,
10+
intercalateSerializedTypes,
811
serializedType,
912
} from '../serialized-type';
1013
import { serializedDependency } from '../serialized-dependency';
1114
import { $refArbitrary } from '../../../../../utils/__tests__/ref.spec';
1215
import { getRelativePath } from '../../../../../utils/ref';
1316
import { pipe } from 'fp-ts/lib/pipeable';
14-
import { arbitrary } from '../../../../../utils/fast-check';
17+
import { arbitrary, nonEmptyArray } from '../../../../../utils/fast-check';
1518
import { none, some } from 'fp-ts/lib/Option';
1619
import { getIOName, getTypeName } from '../../utils';
1720
import { when } from '../../../../../utils/string';
21+
import { head } from 'fp-ts/lib/NonEmptyArray';
1822

1923
const serializedDependencyArbitrary = tuple(string(), string()).map(([name, path]) => serializedDependency(name, path));
2024

@@ -143,4 +147,38 @@ describe('SerializedType', () => {
143147
}),
144148
);
145149
});
150+
it('getSerializedUnionType', () => {
151+
assert(
152+
property(nonEmptyArray(serializedTypeArbitrary), types => {
153+
const intercalated = intercalateSerializedTypes(serializedType(' | ', ',', [], []), types);
154+
const expected =
155+
types.length === 1
156+
? head(types)
157+
: serializedType(
158+
intercalated.type,
159+
`union([${intercalated.io}])`,
160+
[...intercalated.dependencies, serializedDependency('union', 'io-ts')],
161+
intercalated.refs,
162+
);
163+
expect(getSerializedUnionType(types)).toEqual(expected);
164+
}),
165+
);
166+
});
167+
it('getSerializedIntersectionType', () => {
168+
assert(
169+
property(nonEmptyArray(serializedTypeArbitrary), types => {
170+
const intercalated = intercalateSerializedTypes(serializedType(' & ', ',', [], []), types);
171+
const expected =
172+
types.length === 1
173+
? head(types)
174+
: serializedType(
175+
intercalated.type,
176+
`intersection([${intercalated.io}])`,
177+
[...intercalated.dependencies, serializedDependency('intersection', 'io-ts')],
178+
intercalated.refs,
179+
);
180+
expect(getSerializedIntersectionType(types)).toEqual(expected);
181+
}),
182+
);
183+
});
146184
});

0 commit comments

Comments
 (0)