Skip to content

Commit 8597615

Browse files
committed
feat: full $ref support for typescript-3
1 parent 38afb45 commit 8597615

19 files changed

+642
-481
lines changed

src/index.ts

+19-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Decoder } from 'io-ts';
1+
import { Decoder, literal, type, union } from 'io-ts';
22
import { FSEntity, write } from './utils/fs';
33
import * as path from 'path';
44
import * as $RefParser from 'json-schema-ref-parser';
@@ -28,13 +28,9 @@ const getUnsafe: <E, A>(e: Either<E, A>) => A = either.fold(e => {
2828
export const generate = <A>(options: GenerateOptions<A>): TaskEither<unknown, void> =>
2929
taskEither.tryCatch(async () => {
3030
const cwd = process.cwd();
31-
log('cwd', cwd);
32-
3331
const out = path.isAbsolute(options.out) ? options.out : path.resolve(cwd, options.out);
34-
log('out', out);
35-
3632
const spec = path.isAbsolute(options.spec) ? options.spec : path.resolve(cwd, options.spec);
37-
log('spec', spec);
33+
log('Processing', spec);
3834

3935
const $refs = await $RefParser.resolve(spec, {
4036
dereference: {
@@ -46,15 +42,18 @@ export const generate = <A>(options: GenerateOptions<A>): TaskEither<unknown, vo
4642
Object.entries($refs.values()),
4743
array.reduce({}, (acc, [fullPath, spec]) => {
4844
const relative = path.relative(cwd, fullPath);
49-
log('Decoding', relative);
50-
const decoded = reportIfFailed(options.decoder.decode(spec));
51-
if (isLeft(decoded)) {
52-
log('Unable to decode', relative, 'as OpenAPI spec. Treat it as an arbitrary json');
45+
const specLike = specLikeCodec.decode(spec);
46+
if (isLeft(specLike)) {
47+
log('Unable to decode', relative, 'as spec. Treat it as an arbitrary json.');
48+
// this is not a spec - treat as arbitrary json
5349
return acc;
5450
}
51+
// use getUnsafe to fail fast if unable to decode a spec
52+
const decoded = getUnsafe(reportIfFailed(options.decoder.decode(spec)));
53+
log('Decoded', relative);
5554
return {
5655
...acc,
57-
[relative]: decoded.right,
56+
[relative]: decoded,
5857
};
5958
}),
6059
);
@@ -68,3 +67,12 @@ export const generate = <A>(options: GenerateOptions<A>): TaskEither<unknown, vo
6867

6968
log('Done');
7069
}, identity);
70+
71+
const specLikeCodec = union([
72+
type({
73+
swagger: literal('2.0'),
74+
}),
75+
type({
76+
openapi: union([literal('3.0.0'), literal('3.0.1'), literal('3.0.2')]),
77+
}),
78+
]);

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

+13-36
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
SerializedType,
88
} from '../../common/data/serialized-type';
99
import { pipe } from 'fp-ts/lib/pipeable';
10-
import { exists, getOrElse, map, none, Option, some } from 'fp-ts/lib/Option';
10+
import { getOrElse, map, Option } from 'fp-ts/lib/Option';
1111
import { flatten } from 'fp-ts/lib/Array';
1212
import { serializeOperationResponses } from './responses-object';
1313
import { fromArray, head, NonEmptyArray } from 'fp-ts/lib/NonEmptyArray';
@@ -22,7 +22,6 @@ import {
2222
getSerializedKindDependency,
2323
serializedDependency,
2424
} from '../../common/data/serialized-dependency';
25-
import { identity } from 'fp-ts/lib/function';
2625
import { concatIf, concatIfL } from '../../../../utils/array';
2726
import { unless, when } from '../../../../utils/string';
2827
import { Context, getJSDoc, getURL, HTTPMethod, getKindValue } from '../../common/utils';
@@ -150,6 +149,7 @@ const getParameters = combineReader(
150149
}
151150
}
152151
} else {
152+
// if parameter has already been processed then skip it
153153
if (contains(parameter, processedParameters)) {
154154
continue;
155155
}
@@ -169,7 +169,11 @@ const getParameters = combineReader(
169169
break;
170170
}
171171
case 'query': {
172-
const serialized = serializeQueryParameter(from, parameter);
172+
const serialized = pipe(
173+
serializeParameterObject(from, parameter),
174+
either.map(getSerializedPropertyType(parameter.name, isRequired(parameter))),
175+
either.map(fromSerializedType(isRequired(parameter))),
176+
);
173177
if (isLeft(serialized)) {
174178
return serialized;
175179
}
@@ -231,6 +235,7 @@ export const serializeOperationObject = combineReader(
231235
pathItem: PathItemObject,
232236
): Either<Error, SerializedType> => {
233237
const parameters = getParameters(from, operation, pathItem);
238+
const operationName = getOperationName(operation, method);
234239

235240
const serializedResponses = serializeOperationResponses(from, operation.responses);
236241

@@ -274,33 +279,26 @@ export const serializeOperationObject = combineReader(
274279
const hasBodyParameters = parameters.bodyParameters.length > 0;
275280
const hasParameters = hasQueryParameters || hasBodyParameters;
276281

277-
const argsName = concatIf(hasParameters, parameters.pathParameters.map(p => p.name), [
278-
'parameters',
279-
]).join(',');
280282
const argsType = concatIfL(hasParameters, parameters.serializedPathParameters.map(p => p.type), () => [
281283
`parameters: { ${serializedParameters.type} }`,
282284
]).join(',');
283285

284-
const paramsSummary = serializeParametersDescription([
285-
...parameters.queryParameters,
286-
...parameters.bodyParameters,
287-
]);
288-
const operationName = getOperationName(operation, method);
289-
290286
const type = `
291-
${getJSDoc(array.compact([deprecated, operation.summary, paramsSummary]))}
287+
${getJSDoc(array.compact([deprecated, operation.summary]))}
292288
readonly ${operationName}: (${argsType}) => ${getKindValue(kind, serializedResponses.type)};
293289
`;
294290

295-
const serializedUrl = getURL(url, parameters.serializedPathParameters);
291+
const argsName = concatIf(hasParameters, parameters.pathParameters.map(p => p.name), [
292+
'parameters',
293+
]).join(',');
296294

297295
const io = `
298296
${operationName}: (${argsName}) => {
299297
${when(hasParameters, `const encoded = partial({ ${serializedParameters.io} }).encode(parameters);`)}
300298
301299
return e.httpClient.chain(
302300
e.httpClient.request({
303-
url: ${serializedUrl},
301+
url: ${getURL(url, parameters.serializedPathParameters)},
304302
method: '${method}',
305303
${when(hasQueryParameters, 'query: encoded.query,')}
306304
${when(hasBodyParameters, 'body: encoded.body,')}
@@ -341,27 +339,6 @@ const getOperationName = (operation: OperationObject, httpMethod: string) =>
341339
getOrElse(() => httpMethod),
342340
);
343341

344-
const serializeParametersDescription = (
345-
parameters: Array<QueryParameterObject | BodyParameterObject>,
346-
): Option<string> => {
347-
const hasRequiredParameters = parameters.some(p =>
348-
pipe(
349-
p.required,
350-
exists(identity),
351-
),
352-
);
353-
return parameters.length === 0
354-
? none
355-
: some(hasRequiredParameters ? '@param { object } parameters' : '@param { object } [parameters]');
356-
};
357-
358-
const serializeQueryParameter = (from: Ref, parameter: QueryParameterObject): Either<Error, SerializedParameter> =>
359-
pipe(
360-
serializeParameterObject(from, parameter),
361-
either.map(getSerializedPropertyType(parameter.name, isRequired(parameter))),
362-
either.map(fromSerializedType(isRequired(parameter))),
363-
);
364-
365342
const serializeBodyParameterObjects = (
366343
from: Ref,
367344
parameters: NonEmptyArray<BodyParameterObject>,

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

+1-27
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import { serializeNonArraySchemaObject, serializeSchemaObject } from '../schema-object';
1+
import { serializeSchemaObject } from '../schema-object';
22
import {
33
getSerializedArrayType,
44
getSerializedDictionaryType,
55
getSerializedObjectType,
66
getSerializedPropertyType,
77
getSerializedRecursiveType,
88
getSerializedRefType,
9-
serializedType,
109
} from '../../../common/data/serialized-type';
11-
import { serializedDependency } from '../../../common/data/serialized-dependency';
1210
import { Either, right } from 'fp-ts/lib/Either';
1311
import { assert, constant, property, record, string } from 'fast-check';
1412
import { $refArbitrary } from '../../../../../utils/__tests__/ref.spec';
@@ -24,30 +22,6 @@ const chainEither = <A, EB, B>(f: (a: A) => Either<EB, B>) => <EA>(fa: Either<EA
2422
);
2523

2624
describe('SchemaObject', () => {
27-
describe('serializeNonArraySchemaObject', () => {
28-
describe('should serialize', () => {
29-
it('string', () => {
30-
expect(serializeNonArraySchemaObject({ type: 'string', format: none, deprecated: none })).toEqual(
31-
right(serializedType('string', 'string', [serializedDependency('string', 'io-ts')], [])),
32-
);
33-
});
34-
it('boolean', () => {
35-
expect(serializeNonArraySchemaObject({ type: 'boolean', format: none, deprecated: none })).toEqual(
36-
right(serializedType('boolean', 'boolean', [serializedDependency('boolean', 'io-ts')], [])),
37-
);
38-
});
39-
it('integer', () => {
40-
expect(serializeNonArraySchemaObject({ type: 'integer', format: none, deprecated: none })).toEqual(
41-
right(serializedType('number', 'number', [serializedDependency('number', 'io-ts')], [])),
42-
);
43-
});
44-
it('number', () => {
45-
expect(serializeNonArraySchemaObject({ type: 'number', format: none, deprecated: none })).toEqual(
46-
right(serializedType('number', 'number', [serializedDependency('number', 'io-ts')], [])),
47-
);
48-
});
49-
});
50-
});
5125
describe('serializeSchemaObject', () => {
5226
describe('array', () => {
5327
it('should serialize using getSerializedArrayType', () => {

0 commit comments

Comments
 (0)