Skip to content

Commit 3203a63

Browse files
committed
feat: query serialization support in swagger-2 and openapi-3
BREAKING CHANGE: operations now always treat query/body parameters as Option (no ?:) BREAKING CHANGE: HTTPClient.request now receives query already serialized to string according to spec BREAKING CHANGE: ArrayQueryParameterObject.items.type should be a primitive BREAKING CHANGE: SerializedType/SerialiedFragment/SerializedParameter now filter out repeating dependencies and refs on construction closes #74
1 parent cf5cb6a commit 3203a63

30 files changed

+1189
-515
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { getCollectionSeparator, serializeQueryParameterObject } from '../operation-object';
2+
import { fromString } from '../../../../../utils/ref';
3+
import { either } from 'fp-ts';
4+
import { pipe } from 'fp-ts/lib/pipeable';
5+
import {
6+
ArrayParameterObjectCollectionFormat,
7+
ArrayQueryParameterObjectCodec,
8+
} from '../../../../../schema/2.0/parameter-object';
9+
import { combineEither, sequenceTEither } from '@devexperts/utils/dist/adt/either.utils';
10+
import { reportIfFailed } from '../../../../../utils/io-ts';
11+
import { serializedFragment } from '../../../common/data/serialized-fragment';
12+
import {
13+
getSerializedArrayType,
14+
getSerializedIntegerType,
15+
getSerializedOptionalType,
16+
} from '../../../common/data/serialized-type';
17+
import { utilsRef } from '../../../common/bundled/utils';
18+
import { serializedDependency } from '../../../common/data/serialized-dependency';
19+
import { serializeParameterObject } from '../parameter-object';
20+
21+
describe('Operation Object serialization', () => {
22+
it('serializeArrayQueryParameter', () => {
23+
const from = fromString('#/from');
24+
const required = false;
25+
const target = 'parameters.query';
26+
const collectionFormat: ArrayParameterObjectCollectionFormat = 'multi' as ArrayParameterObjectCollectionFormat;
27+
const name = 'queryParam';
28+
const spec = ArrayQueryParameterObjectCodec.decode({
29+
in: 'query',
30+
name,
31+
type: 'array',
32+
required,
33+
items: {
34+
type: 'integer',
35+
},
36+
collectionFormat,
37+
});
38+
39+
const serialized = pipe(
40+
sequenceTEither(from, reportIfFailed(spec)),
41+
either.chain(([from, spec]) =>
42+
pipe(
43+
serializeParameterObject(from, spec),
44+
either.map(serialized => getSerializedOptionalType(required, serialized)),
45+
either.chain(serialized => serializeQueryParameterObject(from, spec, serialized, target)),
46+
),
47+
),
48+
);
49+
50+
const expected = combineEither(from, utilsRef, (from, utilsRef) => {
51+
const type = pipe(
52+
getSerializedIntegerType(from, utilsRef),
53+
getSerializedArrayType(),
54+
serialized => getSerializedOptionalType(required, serialized),
55+
);
56+
const encoded = `${type.io}.encode(${target}.${name})`;
57+
switch (collectionFormat) {
58+
case 'csv':
59+
case 'pipes':
60+
case 'ssv':
61+
case 'tsv': {
62+
const separator = getCollectionSeparator(collectionFormat);
63+
return serializedFragment(
64+
`${name}=\${${encoded}.join('${separator}')}`,
65+
type.dependencies,
66+
type.refs,
67+
);
68+
}
69+
case 'multi': {
70+
const process = `.map(value => \`${name}=\${value}\`).join('&')`;
71+
if (required) {
72+
return serializedFragment(`\${${encoded}${process}}`, type.dependencies, type.refs);
73+
} else {
74+
return serializedFragment(
75+
`\${pipe(${encoded}, option.fromNullable, option.map(value => value${process}), option.getOrElse(() => ''))}`,
76+
[...type.dependencies, serializedDependency('option', 'fp-ts')],
77+
type.refs,
78+
);
79+
}
80+
}
81+
}
82+
});
83+
expect(serialized).toEqual(expected);
84+
});
85+
});

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { $refArbitrary } from '../../../../../utils/__tests__/ref.spec';
33
import {
44
getSerializedIntersectionType,
55
getSerializedObjectType,
6-
getSerializedPropertyType,
6+
getSerializedOptionPropertyType,
77
getSerializedRecursiveType,
88
getSerializedRefType,
99
SERIALIZED_STRING_TYPE,
@@ -33,7 +33,7 @@ describe('SchemaObject serializer', () => {
3333
const expected = pipe(
3434
ref,
3535
getSerializedRefType(ref),
36-
getSerializedPropertyType('recursive', true),
36+
getSerializedOptionPropertyType('recursive', true),
3737
getSerializedObjectType(),
3838
getSerializedRecursiveType(ref, true),
3939
);
@@ -67,9 +67,9 @@ describe('SchemaObject serializer', () => {
6767
const expected = pipe(
6868
ref,
6969
getSerializedRefType(ref),
70-
getSerializedPropertyType('recursive', true),
70+
getSerializedOptionPropertyType('recursive', true),
7171
getSerializedObjectType(),
72-
getSerializedPropertyType('children', true),
72+
getSerializedOptionPropertyType('children', true),
7373
getSerializedObjectType(),
7474
getSerializedRecursiveType(ref, true),
7575
);
@@ -111,7 +111,7 @@ describe('SchemaObject serializer', () => {
111111
const expected = pipe(
112112
ref,
113113
getSerializedRefType(ref),
114-
getSerializedPropertyType('self', true),
114+
getSerializedOptionPropertyType('self', true),
115115
getSerializedObjectType(),
116116
serialized => getSerializedIntersectionType([SERIALIZED_STRING_TYPE, serialized]),
117117
getSerializedRecursiveType(ref, true),
@@ -153,9 +153,9 @@ describe('SchemaObject serializer', () => {
153153
const expected = pipe(
154154
ref,
155155
getSerializedRefType(ref),
156-
getSerializedPropertyType('self', true),
156+
getSerializedOptionPropertyType('self', true),
157157
getSerializedObjectType(),
158-
getSerializedPropertyType('nested', true),
158+
getSerializedOptionPropertyType('nested', true),
159159
getSerializedObjectType(),
160160
serialized => getSerializedIntersectionType([SERIALIZED_STRING_TYPE, serialized]),
161161
getSerializedRecursiveType(ref, true),
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
import { ItemsObject } from '../../../../schema/2.0/items-object';
22
import {
33
getSerializedArrayType,
4+
getSerializedIntegerType,
5+
getSerializedStringType,
46
SERIALIZED_BOOLEAN_TYPE,
57
SERIALIZED_NUMBER_TYPE,
6-
SERIALIZED_STRING_TYPE,
78
SerializedType,
89
} from '../../common/data/serialized-type';
10+
import { Ref } from '../../../../utils/ref';
11+
import { utilsRef } from '../../common/bundled/utils';
12+
import { pipe } from 'fp-ts/lib/pipeable';
13+
import { either } from 'fp-ts';
14+
import { Either, right } from 'fp-ts/lib/Either';
915

10-
export const serializeItemsObject = (itemsObject: ItemsObject): SerializedType => {
16+
export const serializeItemsObject = (from: Ref, itemsObject: ItemsObject): Either<Error, SerializedType> => {
1117
switch (itemsObject.type) {
1218
case 'array': {
13-
return getSerializedArrayType()(serializeItemsObject(itemsObject.items));
19+
return pipe(
20+
serializeItemsObject(from, itemsObject.items),
21+
either.map(getSerializedArrayType()),
22+
);
1423
}
1524
case 'string': {
16-
return SERIALIZED_STRING_TYPE;
25+
return right(getSerializedStringType(itemsObject.format));
26+
}
27+
case 'number': {
28+
return right(SERIALIZED_NUMBER_TYPE);
1729
}
18-
case 'number':
1930
case 'integer': {
20-
return SERIALIZED_NUMBER_TYPE;
31+
return pipe(
32+
utilsRef,
33+
either.map(utilsRef => getSerializedIntegerType(from, utilsRef)),
34+
);
2135
}
2236
case 'boolean': {
23-
return SERIALIZED_BOOLEAN_TYPE;
37+
return right(SERIALIZED_BOOLEAN_TYPE);
2438
}
2539
}
2640
};

0 commit comments

Comments
 (0)