Skip to content

Commit 256dc87

Browse files
committed
feat: abstract from data structure in client
BREAKING CHANGE: apiClient dependency was renamed to httpClient BREAKING CHANGE: APIClient was renamed to HTTPClient<F> and now extends MonadThrow<F> BREAKING CHANGE: FullAPIRequest/APIRequest were joined and renamed to Request closes #48
1 parent 4faad00 commit 256dc87

File tree

10 files changed

+393
-162
lines changed

10 files changed

+393
-162
lines changed

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

+20-25
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ import {
1717
SerializedParameter,
1818
serializedParameter,
1919
} from '../../common/data/serialized-parameter';
20-
import { EMPTY_DEPENDENCIES, serializedDependency } from '../../common/data/serialized-dependency';
20+
import {
21+
EMPTY_DEPENDENCIES,
22+
getSerializedKindDependency,
23+
serializedDependency,
24+
} from '../../common/data/serialized-dependency';
2125
import { identity } from 'fp-ts/lib/function';
2226
import { concatIf, concatIfL } from '../../../../utils/array';
2327
import { unless, when } from '../../../../utils/string';
24-
import { Context, getJSDoc, getURL, HTTPMethod } from '../../common/utils';
28+
import { Context, getJSDoc, getURL, HTTPMethod, getKindValue } from '../../common/utils';
2529
import { Either, isLeft, right } from 'fp-ts/lib/Either';
2630
import { array, either, nonEmptyArray, option } from 'fp-ts';
2731
import { combineEither, sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
@@ -49,6 +53,7 @@ import { serializeSchemaObject } from './schema-object';
4953
import { traverseArrayEither } from '../../../../utils/either';
5054
import { PathItemObject } from '../../../../schema/2.0/path-item-object';
5155
import { Eq, eqString, getStructEq } from 'fp-ts/lib/Eq';
56+
import { Kind } from '../../../../utils/types';
5257

5358
interface Parameters {
5459
readonly pathParameters: PathParameterObject[];
@@ -221,6 +226,7 @@ export const serializeOperationObject = combineReader(
221226
from: Ref,
222227
url: string,
223228
method: HTTPMethod,
229+
kind: Kind,
224230
operation: OperationObject,
225231
pathItem: PathItemObject,
226232
): Either<Error, SerializedType> => {
@@ -283,7 +289,7 @@ export const serializeOperationObject = combineReader(
283289

284290
const type = `
285291
${getJSDoc(array.compact([deprecated, operation.summary, paramsSummary]))}
286-
readonly ${operationName}: (${argsType}) => LiveData<Error, ${serializedResponses.type}>;
292+
readonly ${operationName}: (${argsType}) => ${getKindValue(kind, serializedResponses.type)};
287293
`;
288294

289295
const serializedUrl = getURL(url, parameters.serializedPathParameters);
@@ -292,41 +298,30 @@ export const serializeOperationObject = combineReader(
292298
${operationName}: (${argsName}) => {
293299
${when(hasParameters, `const encoded = partial({ ${serializedParameters.io} }).encode(parameters);`)}
294300
295-
return e.apiClient
296-
.request({
301+
return e.httpClient.chain(
302+
e.httpClient.request({
297303
url: ${serializedUrl},
298304
method: '${method}',
299305
${when(hasQueryParameters, 'query: encoded.query,')}
300306
${when(hasBodyParameters, 'body: encoded.body,')}
301-
})
302-
.pipe(
303-
map(data =>
304-
pipe(
305-
data,
306-
chain(value =>
307-
fromEither<Error, ${serializedResponses.type}>(
308-
pipe(
309-
${serializedResponses.io}.decode(value),
310-
mapLeft(ResponseValidationError.create),
311-
),
312-
),
313-
),
314-
),
307+
}),
308+
value =>
309+
pipe(
310+
${serializedResponses.io}.decode(value),
311+
either.mapLeft(ResponseValidationError.create),
312+
either.fold(error => e.httpClient.throwError(error), decoded => e.httpClient.of(decoded)),
315313
),
316-
);
314+
);
317315
},
318316
`;
319317

320318
const dependencies = concatIfL(
321319
hasParameters,
322320
[
323-
serializedDependency('map', 'rxjs/operators'),
324-
serializedDependency('fromEither', '@devexperts/remote-data-ts'),
325-
serializedDependency('chain', '@devexperts/remote-data-ts'),
326321
serializedDependency('ResponseValidationError', getRelativePath(from, clientRef)),
327-
serializedDependency('LiveData', '@devexperts/rx-utils/dist/rd/live-data.utils'),
328322
serializedDependency('pipe', 'fp-ts/lib/pipeable'),
329-
serializedDependency('mapLeft', 'fp-ts/lib/Either'),
323+
serializedDependency('either', 'fp-ts'),
324+
getSerializedKindDependency(kind),
330325
...flatten(parameters.serializedPathParameters.map(parameter => parameter.dependencies)),
331326
...serializedResponses.dependencies,
332327
...serializedParameters.dependencies,

src/language/typescript/2.0-rx/serializers/path-item-object.ts

+66-28
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { pipe } from 'fp-ts/lib/pipeable';
44
import { isSome, map, Option } from 'fp-ts/lib/Option';
55
import { serializeOperationObject } from './operation-object';
66
import { array, flatten } from 'fp-ts/lib/Array';
7-
import { Dictionary } from '../../../../utils/types';
7+
import { Dictionary, Kind } from '../../../../utils/types';
88
import { file, File } from '../../../../utils/fs';
99
import { serializedDependency, serializeDependencies } from '../../common/data/serialized-dependency';
1010
import { decapitalize } from '@devexperts/utils/dist/string';
@@ -21,43 +21,43 @@ import { ask } from 'fp-ts/lib/Reader';
2121
import { Context } from '../../common/utils';
2222

2323
const serializePath = combineReader(ask<Context>(), serializeOperationObject, (e, serializeOperationObject) => {
24-
const run = (from: Ref, url: string, item: PathItemObject): Either<Error, SerializedType> => {
24+
const run = (from: Ref, url: string, kind: Kind, item: PathItemObject): Either<Error, SerializedType> => {
2525
if (isSome(item.$ref)) {
2626
const $ref = item.$ref.value;
2727
return pipe(
2828
e.resolveRef({ $ref }),
2929
PathItemObjectCodec.decode,
3030
either.mapLeft(() => new Error(`Unable to resolve PathItem $ref: "${$ref}"`)),
31-
either.chain(resolved => run(from, url, resolved)),
31+
either.chain(resolved => run(from, url, kind, resolved)),
3232
);
3333
} else {
3434
const get = pipe(
3535
item.get,
36-
map(operation => serializeOperationObject(from, url, 'GET', operation, item)),
36+
map(operation => serializeOperationObject(from, url, 'GET', kind, operation, item)),
3737
);
3838
const put = pipe(
3939
item.put,
40-
map(operation => serializeOperationObject(from, url, 'PUT', operation, item)),
40+
map(operation => serializeOperationObject(from, url, 'PUT', kind, operation, item)),
4141
);
4242
const post = pipe(
4343
item.post,
44-
map(operation => serializeOperationObject(from, url, 'POST', operation, item)),
44+
map(operation => serializeOperationObject(from, url, 'POST', kind, operation, item)),
4545
);
4646
const remove = pipe(
4747
item.delete,
48-
map(operation => serializeOperationObject(from, url, 'DELETE', operation, item)),
48+
map(operation => serializeOperationObject(from, url, 'DELETE', kind, operation, item)),
4949
);
5050
const options = pipe(
5151
item.options,
52-
map(operation => serializeOperationObject(from, url, 'OPTIONS', operation, item)),
52+
map(operation => serializeOperationObject(from, url, 'OPTIONS', kind, operation, item)),
5353
);
5454
const head = pipe(
5555
item.head,
56-
map(operation => serializeOperationObject(from, url, 'HEAD', operation, item)),
56+
map(operation => serializeOperationObject(from, url, 'HEAD', kind, operation, item)),
5757
);
5858
const patch = pipe(
5959
item.patch,
60-
map(operation => serializeOperationObject(from, url, 'PATCH', operation, item)),
60+
map(operation => serializeOperationObject(from, url, 'PATCH', kind, operation, item)),
6161
);
6262
const operations = [get, put, post, remove, options, head, patch];
6363
return pipe(
@@ -74,34 +74,72 @@ const serializePath = combineReader(ask<Context>(), serializeOperationObject, (e
7474
export const serializePathGroup = combineReader(
7575
serializePath,
7676
serializePath => (from: Ref, name: string, group: Dictionary<PathItemObject>): Either<Error, File> => {
77-
const serialized = pipe(
77+
const serializedHKT = pipe(
7878
group,
79-
record.collect((url, item) => serializePath(from, url, item)),
79+
record.collect((url, item) => serializePath(from, url, 'HKT', item)),
8080
sequenceEither,
8181
either.map(foldSerializedTypes),
8282
);
8383

84-
return combineEither(serialized, clientRef, (serialized, clientRef) => {
85-
const dependencies = serializeDependencies([
86-
...serialized.dependencies,
87-
serializedDependency('asks', 'fp-ts/lib/Reader'),
88-
serializedDependency('APIClient', getRelativePath(from, clientRef)),
89-
]);
90-
return file(
91-
`${from.name}.ts`,
92-
`
84+
const serializedKind = pipe(
85+
group,
86+
record.collect((url, item) => serializePath(from, url, '*', item)),
87+
sequenceEither,
88+
either.map(foldSerializedTypes),
89+
);
90+
91+
const serializedKind2 = pipe(
92+
group,
93+
record.collect((url, item) => serializePath(from, url, '* -> *', item)),
94+
sequenceEither,
95+
either.map(foldSerializedTypes),
96+
);
97+
98+
return combineEither(
99+
serializedHKT,
100+
serializedKind,
101+
serializedKind2,
102+
clientRef,
103+
(serializedHKT, serializedKind, serializedKind2, clientRef) => {
104+
const dependencies = serializeDependencies([
105+
...serializedHKT.dependencies,
106+
...serializedKind.dependencies,
107+
...serializedKind2.dependencies,
108+
serializedDependency('HTTPClient', getRelativePath(from, clientRef)),
109+
serializedDependency('HTTPClient1', getRelativePath(from, clientRef)),
110+
serializedDependency('HTTPClient2', getRelativePath(from, clientRef)),
111+
serializedDependency('URIS', 'fp-ts/lib/HKT'),
112+
serializedDependency('URIS2', 'fp-ts/lib/HKT'),
113+
]);
114+
return file(
115+
`${from.name}.ts`,
116+
`
93117
${dependencies}
94118
95-
export interface ${from.name} {
96-
${serialized.type}
119+
export interface ${from.name}<F> {
120+
${serializedHKT.type}
121+
}
122+
123+
export interface ${from.name}1<F extends URIS> {
124+
${serializedKind.type}
125+
}
126+
127+
export interface ${from.name}2<F extends URIS2> {
128+
${serializedKind2.type}
97129
}
98130
99-
export const ${decapitalize(from.name)} = asks((e: { apiClient: APIClient }): ${from.name} => ({
100-
${serialized.io}
101-
}));
131+
export function ${decapitalize(from.name)}<F extends URIS2>(e: { httpClient: HTTPClient2<F> }): ${from.name}2<F>
132+
export function ${decapitalize(from.name)}<F extends URIS>(e: { httpClient: HTTPClient1<F> }): ${from.name}1<F>
133+
export function ${decapitalize(from.name)}<F>(e: { httpClient: HTTPClient<F> }): ${from.name}<F>;
134+
export function ${decapitalize(from.name)}<F>(e: { httpClient: HTTPClient<F> }): ${from.name}<F>; {
135+
return {
136+
${serializedHKT.io}
137+
}
138+
}
102139
`,
103-
);
104-
});
140+
);
141+
},
142+
);
105143
},
106144
);
107145

Original file line numberDiff line numberDiff line change
@@ -1,43 +1,108 @@
11
import { PathsObject } from '../../../../schema/2.0/paths-object';
2-
import { directory, Directory } from '../../../../utils/fs';
2+
import { directory, Directory, File, file } from '../../../../utils/fs';
33
import { serializePathGroup, serializePathItemObjectTags } from './path-item-object';
4-
import { CONTROLLERS_DIRECTORY } from '../../common/utils';
4+
import { CONTROLLERS_DIRECTORY, getControllerName } from '../../common/utils';
55
import { pipe } from 'fp-ts/lib/pipeable';
6-
import { either, option, record } from 'fp-ts';
7-
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
8-
import { Either } from 'fp-ts/lib/Either';
9-
import { addPathParts, Ref } from '../../../../utils/ref';
6+
import { array, either, option, record } from 'fp-ts';
7+
import { combineEither, sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
8+
import { Either, isLeft, right } from 'fp-ts/lib/Either';
9+
import { addPathParts, getRelativePath, Ref } from '../../../../utils/ref';
1010
import { Dictionary } from '../../../../utils/types';
1111
import { PathItemObject } from '../../../../schema/2.0/path-item-object';
12-
import { camelize } from '@devexperts/utils/dist/string';
12+
import { camelize, decapitalize } from '@devexperts/utils/dist/string';
1313
import { combineReader } from '@devexperts/utils/dist/adt/reader.utils';
14+
import { serializedDependency, serializeDependencies } from '../../common/data/serialized-dependency';
15+
import { clientRef } from '../../common/client';
1416

1517
export const serializePaths = combineReader(
1618
serializePathGroup,
17-
serializePathGroup => (from: Ref, paths: PathsObject): Either<Error, Directory> =>
18-
pipe(
19-
groupPathsByTag(paths),
20-
record.collect((name, group) => {
21-
return pipe(
19+
serializePathGroup => (from: Ref, paths: PathsObject): Either<Error, Directory> => {
20+
const groupped = groupPathsByTag(paths);
21+
const files = pipe(
22+
groupped,
23+
record.collect((name, group) =>
24+
pipe(
2225
from,
23-
addPathParts(`${name}Controller`),
26+
addPathParts(name),
2427
either.chain(from => serializePathGroup(from, name, group)),
25-
);
26-
}),
28+
),
29+
),
2730
sequenceEither,
28-
either.map(serialized => directory(CONTROLLERS_DIRECTORY, serialized)),
29-
),
31+
);
32+
const index = pipe(
33+
from,
34+
addPathParts(CONTROLLERS_DIRECTORY),
35+
either.chain(from => serializePathsIndex(from, record.keys(groupped))),
36+
);
37+
return combineEither(files, index, (files, index) => directory(CONTROLLERS_DIRECTORY, [...files, index]));
38+
},
3039
);
3140

41+
const serializePathsIndex = (from: Ref, pathNames: string[]): Either<Error, File> => {
42+
if (isLeft(clientRef)) {
43+
return clientRef;
44+
}
45+
const pathToClient = getRelativePath(from, clientRef.right);
46+
const dependencies = serializeDependencies([
47+
...array.flatten(
48+
pathNames.map(name => {
49+
const p = `./${name}`;
50+
return [
51+
serializedDependency(decapitalize(name), p),
52+
serializedDependency(name, p),
53+
serializedDependency(`${name}1`, p),
54+
serializedDependency(`${name}2`, p),
55+
];
56+
}),
57+
),
58+
serializedDependency('URIS', 'fp-ts/lib/HKT'),
59+
serializedDependency('URIS2', 'fp-ts/lib/HKT'),
60+
serializedDependency('URIS2', 'fp-ts/lib/HKT'),
61+
serializedDependency('HTTPClient', pathToClient),
62+
serializedDependency('HTTPClient1', pathToClient),
63+
serializedDependency('HTTPClient2', pathToClient),
64+
]);
65+
const content = `
66+
export interface Controllers<F> {
67+
${pathNames.map(name => `${decapitalize(name)}: ${name}<F>;`).join('\n')}
68+
}
69+
export interface Controllers1<F extends URIS> {
70+
${pathNames.map(name => `${decapitalize(name)}: ${name}1<F>;`).join('\n')}
71+
}
72+
export interface Controllers2<F extends URIS2> {
73+
${pathNames.map(name => `${decapitalize(name)}: ${name}2<F>;`).join('\n')}
74+
}
75+
76+
export function controllers<F extends URIS2>(e: { httpClient: HTTPClient2<F> }): Controllers2<F>
77+
export function controllers<F extends URIS>(e: { httpClient: HTTPClient1<F> }): Controllers1<F>
78+
export function controllers<F>(e: { httpClient: HTTPClient<F> }): Controllers<F>;
79+
export function controllers<F>(e: { httpClient: HTTPClient<F> }): Controllers<F> {
80+
return {
81+
${pathNames.map(name => `${decapitalize(name)}: ${decapitalize(name)}(e),`).join('\n')}
82+
}
83+
}
84+
`;
85+
return right(
86+
file(
87+
`${from.name}.ts`,
88+
`
89+
${dependencies}
90+
91+
${content}
92+
`,
93+
),
94+
);
95+
};
96+
3297
const groupPathsByTag = (pathsObject: PathsObject): Dictionary<Dictionary<PathItemObject>> => {
3398
const keys = Object.keys(pathsObject);
3499
const result: Record<string, PathsObject> = {};
35100
for (const key of keys) {
36101
const path = pathsObject[key];
37102
const tag = pipe(
38103
serializePathItemObjectTags(path),
39-
option.map(p => camelize(p, false)),
40-
option.getOrElse(() => 'Unknown'),
104+
option.map(p => getControllerName(camelize(p, false))),
105+
option.getOrElse(() => getControllerName('Unknown')),
41106
);
42107
result[tag] = {
43108
...(result[tag] || {}),
@@ -46,3 +111,4 @@ const groupPathsByTag = (pathsObject: PathsObject): Dictionary<Dictionary<PathIt
46111
}
47112
return result;
48113
};
114+

0 commit comments

Comments
 (0)