Skip to content

Commit cc6c57a

Browse files
author
Anton Koshkin
committed
feat: add allOf support
closes #11
1 parent f4bc736 commit cc6c57a

File tree

2 files changed

+80
-46
lines changed

2 files changed

+80
-46
lines changed

src/language/typescript.ts

+57-37
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
TAllOfSchemaObject,
23
TBodyParameterObject,
34
TDefinitionsObject,
45
TNonArrayItemsObject,
@@ -8,6 +9,7 @@ import {
89
TPathParameterObject,
910
TPathsObject,
1011
TQueryParameterObject,
12+
TReferenceSchemaObject,
1113
TResponseObject,
1214
TResponsesObject,
1315
TSchemaObject,
@@ -32,7 +34,7 @@ import { intercalate } from 'fp-ts/lib/Foldable2v';
3234
import { collect, lookup } from 'fp-ts/lib/Record';
3335
import { identity } from 'fp-ts/lib/function';
3436

35-
const EMPTY_DEPENDENCIES: TDepdendency[] = [];
37+
const EMPTY_DEPENDENCIES: TDependency[] = [];
3638
const EMPTY_REFS: string[] = [];
3739
const SUCCESSFUL_CODES = ['200', 'default'];
3840

@@ -41,17 +43,17 @@ const concatIf = <A>(condition: boolean, as: A[], a: A[]): A[] => concatIfL(cond
4143
const unless = (condition: boolean, a: string): string => (condition ? '' : a);
4244
const when = (condition: boolean, a: string): string => (condition ? a : '');
4345

44-
type TDepdendency = {
46+
type TDependency = {
4547
name: string;
4648
path: string;
4749
};
4850
type TSerializedType = {
4951
type: string;
5052
io: string;
51-
dependencies: TDepdendency[];
53+
dependencies: TDependency[];
5254
refs: string[];
5355
};
54-
const serializedType = (type: string, io: string, dependencies: TDepdendency[], refs: string[]): TSerializedType => ({
56+
const serializedType = (type: string, io: string, dependencies: TDependency[], refs: string[]): TSerializedType => ({
5557
type,
5658
io,
5759
dependencies,
@@ -65,7 +67,7 @@ const serializedParameter = (
6567
type: string,
6668
io: string,
6769
isRequired: boolean,
68-
dependencies: TDepdendency[],
70+
dependencies: TDependency[],
6971
refs: string[],
7072
): TSerializedParameter => ({
7173
type,
@@ -82,7 +84,7 @@ const serializedPathParameter = (
8284
type: string,
8385
io: string,
8486
isRequired: boolean,
85-
dependencies: TDepdendency[],
87+
dependencies: TDependency[],
8688
refs: string[],
8789
): TSerializedPathParameter => ({
8890
name,
@@ -92,15 +94,15 @@ const serializedPathParameter = (
9294
dependencies,
9395
refs,
9496
});
95-
const dependency = (name: string, path: string): TDepdendency => ({
97+
const dependency = (name: string, path: string): TDependency => ({
9698
name,
9799
path,
98100
});
99101
const dependencyOption = dependency('Option', 'fp-ts/lib/Option');
100102
const dependencyCreateOptionFromNullable = dependency('createOptionFromNullable', 'io-ts-types');
101-
const OPTION_DEPENDENCIES: TDepdendency[] = [dependencyOption, dependencyCreateOptionFromNullable];
103+
const OPTION_DEPENDENCIES: TDependency[] = [dependencyOption, dependencyCreateOptionFromNullable];
102104

103-
const monoidDependencies = getArrayMonoid<TDepdendency>();
105+
const monoidDependencies = getArrayMonoid<TDependency>();
104106
const monoidRefs = getArrayMonoid<string>();
105107
const monoidSerializedType = getRecordMonoid<TSerializedType>({
106108
type: monoidString,
@@ -210,47 +212,65 @@ const serializePath = (url: string, item: TPathItemObject, rootName: string, cwd
210212
const get = item.get.map(operation => serializeOperationObject(url, 'GET', operation, rootName, cwd));
211213
const put = item.put.map(operation => serializeOperationObject(url, 'PUT', operation, rootName, cwd));
212214
const post = item.post.map(operation => serializeOperationObject(url, 'POST', operation, rootName, cwd));
213-
const remove = item['delete'].map(operation => serializeOperationObject(url, 'DELETE', operation, rootName, cwd));
215+
const remove = item.delete.map(operation => serializeOperationObject(url, 'DELETE', operation, rootName, cwd));
214216
const options = item.options.map(operation => serializeOperationObject(url, 'OPTIONS', operation, rootName, cwd));
215217
const head = item.head.map(operation => serializeOperationObject(url, 'HEAD', operation, rootName, cwd));
216218
const patch = item.patch.map(operation => serializeOperationObject(url, 'PATCH', operation, rootName, cwd));
217219
const operations = catOptions([get, put, post, remove, options, head, patch]);
218220
return foldSerialized(operations);
219221
};
220222

223+
const is$ref = (a: TReferenceSchemaObject | TAllOfSchemaObject): a is TReferenceSchemaObject =>
224+
Object.prototype.hasOwnProperty.bind(a)('$ref');
225+
221226
const serializeSchemaObject = (schema: TSchemaObject, rootName: string, cwd: string): TSerializedType => {
222227
switch (schema.type) {
223228
case undefined: {
224-
const $ref = schema.$ref;
225-
const defBlock = fromNullable($ref.match(/#\/[^\/]+\//))
226-
.chain(head)
227-
.map(def => def.replace(/[#, \/]/g, ''));
228-
const refFileName = fromNullable($ref.match(/^\.\/[^\/]+\.[^\/]+#/))
229-
.chain(head)
230-
.map(ref => ref.replace(/(\.\/|\.[^/]+#)/g, ''));
231-
const safeType = fromNullable($ref.match(/\/[^\/]+$/))
232-
.chain(head)
233-
.map(type => type.replace(/^\//, ''));
234-
235-
if (safeType.isNone() || defBlock.isNone()) {
236-
throw new Error(`Invalid $ref: ${$ref}`);
229+
if (is$ref(schema)) {
230+
const $ref = schema.$ref;
231+
const defBlock = fromNullable($ref.match(/#\/[^\/]+\//))
232+
.chain(head)
233+
.map(def => def.replace(/[#, \/]/g, ''));
234+
const refFileName = fromNullable($ref.match(/^\.\/[^\/]+\.[^\/]+#/))
235+
.chain(head)
236+
.map(ref => ref.replace(/(\.\/|\.[^/]+#)/g, ''));
237+
const safeType = fromNullable($ref.match(/\/[^\/]+$/))
238+
.chain(head)
239+
.map(type => type.replace(/^\//, ''));
240+
241+
if (safeType.isNone() || defBlock.isNone()) {
242+
throw new Error(`Invalid $ref: ${$ref}`);
243+
}
244+
245+
const type = safeType.value;
246+
247+
const io = getIOName(type);
248+
const isRecursive = rootName === type || rootName === io;
249+
const definitionFilePath = refFileName.isSome()
250+
? getRelativeOutRefPath(cwd, defBlock.value, refFileName.value, type)
251+
: getRelativeRefPath(cwd, defBlock.value, type);
252+
253+
return serializedType(
254+
type,
255+
io,
256+
isRecursive
257+
? EMPTY_DEPENDENCIES
258+
: [dependency(type, definitionFilePath), dependency(io, definitionFilePath)],
259+
[type],
260+
);
237261
}
238262

239-
const type = safeType.value;
240-
241-
const io = getIOName(type);
242-
const isRecursive = rootName === type || rootName === io;
243-
const definitionFilePath = refFileName.isSome()
244-
? getRelativeOutRefPath(cwd, defBlock.value, refFileName.value, type)
245-
: getRelativeRefPath(cwd, defBlock.value, type);
263+
const results = schema.allOf.map(item => serializeSchemaObject(item, rootName, cwd));
264+
const types = results.map(item => item.type);
265+
const ios = results.map(item => item.io);
266+
const dependencies = fold(monoidDependencies)(results.map(item => item.dependencies));
267+
const refs = fold(monoidRefs)(results.map(item => item.refs));
246268

247269
return serializedType(
248-
type,
249-
io,
250-
isRecursive
251-
? EMPTY_DEPENDENCIES
252-
: [dependency(type, definitionFilePath), dependency(io, definitionFilePath)],
253-
[type],
270+
intercalate(monoidString, array)(' & ', types),
271+
`intersection([${intercalate(monoidString, array)(', ', ios)}])`,
272+
[dependency('intersection', 'io-ts'), ...dependencies],
273+
refs,
254274
);
255275
}
256276
case 'string': {
@@ -607,7 +627,7 @@ const getIOName = (name: string): string => `${name}IO`;
607627
const getOperationName = (operation: TOperationObject, httpMethod: string) =>
608628
operation.operationId.getOrElse(httpMethod);
609629

610-
const serializeDependencies = (dependencies: TDepdendency[]): string =>
630+
const serializeDependencies = (dependencies: TDependency[]): string =>
611631
collect(groupBy(dependencies, dependency => dependency.path), (key, dependencies) => {
612632
const names = uniqString(dependencies.toArray().map(dependency => dependency.name));
613633
return `import { ${names.join(',')} } from '${dependencies.head.path}';`;

src/swagger.ts

+23-9
Original file line numberDiff line numberDiff line change
@@ -120,25 +120,24 @@ export const BooleanPropertySchemaObject: t.Tagged<'type', TBooleanPropertySchem
120120
type: t.literal('boolean'),
121121
});
122122

123+
export type TAllOfSchemaObject = TBaseSchemaObjectProps & {
124+
allOf: TSchemaObject[];
125+
description: Option<string>;
126+
type: undefined;
127+
};
123128
export type TReferenceSchemaObject = TReferenceObject &
124129
TBaseSchemaObjectProps & {
125130
type: undefined;
126131
};
127-
export const ReferenceSchemaObject = t.intersection([
128-
ReferenceObject,
129-
t.type({
130-
...BaseSchemaObjectProps,
131-
type: t.literal(undefined as any),
132-
}),
133-
]);
132+
export type TReferenceOrAllOfSchemeObject = TReferenceSchemaObject | TAllOfSchemaObject;
134133

135134
export type TArraySchemaObject = TBaseSchemaObjectProps & {
136135
type: 'array';
137136
items: TSchemaObject;
138137
};
139138

140139
export type TSchemaObject =
141-
| TReferenceSchemaObject
140+
| TReferenceOrAllOfSchemeObject
142141
| TObjectSchemaObject
143142
| TStringPropertySchemaObject
144143
| TNumberPropertySchemaObject
@@ -160,9 +159,24 @@ export const SchemaObject: t.Type<TSchemaObject, mixed> = t.recursion<TSchemaObj
160159
properties: createOptionFromNullable(t.dictionary(t.string, SchemaObject)),
161160
additionalProperties: createOptionFromNullable(SchemaObject),
162161
});
162+
const ReferenceOrAllOfSchemaObject: t.Tagged<'type', TReferenceOrAllOfSchemeObject, mixed> = t.union([
163+
t.intersection([
164+
ReferenceObject,
165+
t.type({
166+
...BaseSchemaObjectProps,
167+
type: t.literal(undefined as any),
168+
}),
169+
]),
170+
t.type({
171+
...BaseSchemaObjectProps,
172+
description: stringOption,
173+
type: t.literal(undefined as any),
174+
allOf: t.array(SchemaObject),
175+
}),
176+
]);
163177

164178
return t.taggedUnion('type', [
165-
ReferenceSchemaObject,
179+
ReferenceOrAllOfSchemaObject,
166180
ArraySchemaObject,
167181
ObjectSchemaObject,
168182
StringPropertySchemaObject,

0 commit comments

Comments
 (0)