Skip to content

Commit

Permalink
feat: abstract from data structure in client
Browse files Browse the repository at this point in the history
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
  • Loading branch information
raveclassic committed Oct 29, 2019
1 parent 4faad00 commit 256dc87
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 162 deletions.
45 changes: 20 additions & 25 deletions src/language/typescript/2.0-rx/serializers/operation-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ import {
SerializedParameter,
serializedParameter,
} from '../../common/data/serialized-parameter';
import { EMPTY_DEPENDENCIES, serializedDependency } from '../../common/data/serialized-dependency';
import {
EMPTY_DEPENDENCIES,
getSerializedKindDependency,
serializedDependency,
} from '../../common/data/serialized-dependency';
import { identity } from 'fp-ts/lib/function';
import { concatIf, concatIfL } from '../../../../utils/array';
import { unless, when } from '../../../../utils/string';
import { Context, getJSDoc, getURL, HTTPMethod } from '../../common/utils';
import { Context, getJSDoc, getURL, HTTPMethod, getKindValue } from '../../common/utils';
import { Either, isLeft, right } from 'fp-ts/lib/Either';
import { array, either, nonEmptyArray, option } from 'fp-ts';
import { combineEither, sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
Expand Down Expand Up @@ -49,6 +53,7 @@ import { serializeSchemaObject } from './schema-object';
import { traverseArrayEither } from '../../../../utils/either';
import { PathItemObject } from '../../../../schema/2.0/path-item-object';
import { Eq, eqString, getStructEq } from 'fp-ts/lib/Eq';
import { Kind } from '../../../../utils/types';

interface Parameters {
readonly pathParameters: PathParameterObject[];
Expand Down Expand Up @@ -221,6 +226,7 @@ export const serializeOperationObject = combineReader(
from: Ref,
url: string,
method: HTTPMethod,
kind: Kind,
operation: OperationObject,
pathItem: PathItemObject,
): Either<Error, SerializedType> => {
Expand Down Expand Up @@ -283,7 +289,7 @@ export const serializeOperationObject = combineReader(

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

const serializedUrl = getURL(url, parameters.serializedPathParameters);
Expand All @@ -292,41 +298,30 @@ export const serializeOperationObject = combineReader(
${operationName}: (${argsName}) => {
${when(hasParameters, `const encoded = partial({ ${serializedParameters.io} }).encode(parameters);`)}
return e.apiClient
.request({
return e.httpClient.chain(
e.httpClient.request({
url: ${serializedUrl},
method: '${method}',
${when(hasQueryParameters, 'query: encoded.query,')}
${when(hasBodyParameters, 'body: encoded.body,')}
})
.pipe(
map(data =>
pipe(
data,
chain(value =>
fromEither<Error, ${serializedResponses.type}>(
pipe(
${serializedResponses.io}.decode(value),
mapLeft(ResponseValidationError.create),
),
),
),
),
}),
value =>
pipe(
${serializedResponses.io}.decode(value),
either.mapLeft(ResponseValidationError.create),
either.fold(error => e.httpClient.throwError(error), decoded => e.httpClient.of(decoded)),
),
);
);
},
`;

const dependencies = concatIfL(
hasParameters,
[
serializedDependency('map', 'rxjs/operators'),
serializedDependency('fromEither', '@devexperts/remote-data-ts'),
serializedDependency('chain', '@devexperts/remote-data-ts'),
serializedDependency('ResponseValidationError', getRelativePath(from, clientRef)),
serializedDependency('LiveData', '@devexperts/rx-utils/dist/rd/live-data.utils'),
serializedDependency('pipe', 'fp-ts/lib/pipeable'),
serializedDependency('mapLeft', 'fp-ts/lib/Either'),
serializedDependency('either', 'fp-ts'),
getSerializedKindDependency(kind),
...flatten(parameters.serializedPathParameters.map(parameter => parameter.dependencies)),
...serializedResponses.dependencies,
...serializedParameters.dependencies,
Expand Down
94 changes: 66 additions & 28 deletions src/language/typescript/2.0-rx/serializers/path-item-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { pipe } from 'fp-ts/lib/pipeable';
import { isSome, map, Option } from 'fp-ts/lib/Option';
import { serializeOperationObject } from './operation-object';
import { array, flatten } from 'fp-ts/lib/Array';
import { Dictionary } from '../../../../utils/types';
import { Dictionary, Kind } from '../../../../utils/types';
import { file, File } from '../../../../utils/fs';
import { serializedDependency, serializeDependencies } from '../../common/data/serialized-dependency';
import { decapitalize } from '@devexperts/utils/dist/string';
Expand All @@ -21,43 +21,43 @@ import { ask } from 'fp-ts/lib/Reader';
import { Context } from '../../common/utils';

const serializePath = combineReader(ask<Context>(), serializeOperationObject, (e, serializeOperationObject) => {
const run = (from: Ref, url: string, item: PathItemObject): Either<Error, SerializedType> => {
const run = (from: Ref, url: string, kind: Kind, item: PathItemObject): Either<Error, SerializedType> => {
if (isSome(item.$ref)) {
const $ref = item.$ref.value;
return pipe(
e.resolveRef({ $ref }),
PathItemObjectCodec.decode,
either.mapLeft(() => new Error(`Unable to resolve PathItem $ref: "${$ref}"`)),
either.chain(resolved => run(from, url, resolved)),
either.chain(resolved => run(from, url, kind, resolved)),
);
} else {
const get = pipe(
item.get,
map(operation => serializeOperationObject(from, url, 'GET', operation, item)),
map(operation => serializeOperationObject(from, url, 'GET', kind, operation, item)),
);
const put = pipe(
item.put,
map(operation => serializeOperationObject(from, url, 'PUT', operation, item)),
map(operation => serializeOperationObject(from, url, 'PUT', kind, operation, item)),
);
const post = pipe(
item.post,
map(operation => serializeOperationObject(from, url, 'POST', operation, item)),
map(operation => serializeOperationObject(from, url, 'POST', kind, operation, item)),
);
const remove = pipe(
item.delete,
map(operation => serializeOperationObject(from, url, 'DELETE', operation, item)),
map(operation => serializeOperationObject(from, url, 'DELETE', kind, operation, item)),
);
const options = pipe(
item.options,
map(operation => serializeOperationObject(from, url, 'OPTIONS', operation, item)),
map(operation => serializeOperationObject(from, url, 'OPTIONS', kind, operation, item)),
);
const head = pipe(
item.head,
map(operation => serializeOperationObject(from, url, 'HEAD', operation, item)),
map(operation => serializeOperationObject(from, url, 'HEAD', kind, operation, item)),
);
const patch = pipe(
item.patch,
map(operation => serializeOperationObject(from, url, 'PATCH', operation, item)),
map(operation => serializeOperationObject(from, url, 'PATCH', kind, operation, item)),
);
const operations = [get, put, post, remove, options, head, patch];
return pipe(
Expand All @@ -74,34 +74,72 @@ const serializePath = combineReader(ask<Context>(), serializeOperationObject, (e
export const serializePathGroup = combineReader(
serializePath,
serializePath => (from: Ref, name: string, group: Dictionary<PathItemObject>): Either<Error, File> => {
const serialized = pipe(
const serializedHKT = pipe(
group,
record.collect((url, item) => serializePath(from, url, item)),
record.collect((url, item) => serializePath(from, url, 'HKT', item)),
sequenceEither,
either.map(foldSerializedTypes),
);

return combineEither(serialized, clientRef, (serialized, clientRef) => {
const dependencies = serializeDependencies([
...serialized.dependencies,
serializedDependency('asks', 'fp-ts/lib/Reader'),
serializedDependency('APIClient', getRelativePath(from, clientRef)),
]);
return file(
`${from.name}.ts`,
`
const serializedKind = pipe(
group,
record.collect((url, item) => serializePath(from, url, '*', item)),
sequenceEither,
either.map(foldSerializedTypes),
);

const serializedKind2 = pipe(
group,
record.collect((url, item) => serializePath(from, url, '* -> *', item)),
sequenceEither,
either.map(foldSerializedTypes),
);

return combineEither(
serializedHKT,
serializedKind,
serializedKind2,
clientRef,
(serializedHKT, serializedKind, serializedKind2, clientRef) => {
const dependencies = serializeDependencies([
...serializedHKT.dependencies,
...serializedKind.dependencies,
...serializedKind2.dependencies,
serializedDependency('HTTPClient', getRelativePath(from, clientRef)),
serializedDependency('HTTPClient1', getRelativePath(from, clientRef)),
serializedDependency('HTTPClient2', getRelativePath(from, clientRef)),
serializedDependency('URIS', 'fp-ts/lib/HKT'),
serializedDependency('URIS2', 'fp-ts/lib/HKT'),
]);
return file(
`${from.name}.ts`,
`
${dependencies}
export interface ${from.name} {
${serialized.type}
export interface ${from.name}<F> {
${serializedHKT.type}
}
export interface ${from.name}1<F extends URIS> {
${serializedKind.type}
}
export interface ${from.name}2<F extends URIS2> {
${serializedKind2.type}
}
export const ${decapitalize(from.name)} = asks((e: { apiClient: APIClient }): ${from.name} => ({
${serialized.io}
}));
export function ${decapitalize(from.name)}<F extends URIS2>(e: { httpClient: HTTPClient2<F> }): ${from.name}2<F>
export function ${decapitalize(from.name)}<F extends URIS>(e: { httpClient: HTTPClient1<F> }): ${from.name}1<F>
export function ${decapitalize(from.name)}<F>(e: { httpClient: HTTPClient<F> }): ${from.name}<F>;
export function ${decapitalize(from.name)}<F>(e: { httpClient: HTTPClient<F> }): ${from.name}<F>; {
return {
${serializedHKT.io}
}
}
`,
);
});
);
},
);
},
);

Expand Down
104 changes: 85 additions & 19 deletions src/language/typescript/2.0-rx/serializers/paths-object.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,108 @@
import { PathsObject } from '../../../../schema/2.0/paths-object';
import { directory, Directory } from '../../../../utils/fs';
import { directory, Directory, File, file } from '../../../../utils/fs';
import { serializePathGroup, serializePathItemObjectTags } from './path-item-object';
import { CONTROLLERS_DIRECTORY } from '../../common/utils';
import { CONTROLLERS_DIRECTORY, getControllerName } from '../../common/utils';
import { pipe } from 'fp-ts/lib/pipeable';
import { either, option, record } from 'fp-ts';
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
import { Either } from 'fp-ts/lib/Either';
import { addPathParts, Ref } from '../../../../utils/ref';
import { array, either, option, record } from 'fp-ts';
import { combineEither, sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
import { Either, isLeft, right } from 'fp-ts/lib/Either';
import { addPathParts, getRelativePath, Ref } from '../../../../utils/ref';
import { Dictionary } from '../../../../utils/types';
import { PathItemObject } from '../../../../schema/2.0/path-item-object';
import { camelize } from '@devexperts/utils/dist/string';
import { camelize, decapitalize } from '@devexperts/utils/dist/string';
import { combineReader } from '@devexperts/utils/dist/adt/reader.utils';
import { serializedDependency, serializeDependencies } from '../../common/data/serialized-dependency';
import { clientRef } from '../../common/client';

export const serializePaths = combineReader(
serializePathGroup,
serializePathGroup => (from: Ref, paths: PathsObject): Either<Error, Directory> =>
pipe(
groupPathsByTag(paths),
record.collect((name, group) => {
return pipe(
serializePathGroup => (from: Ref, paths: PathsObject): Either<Error, Directory> => {
const groupped = groupPathsByTag(paths);
const files = pipe(
groupped,
record.collect((name, group) =>
pipe(
from,
addPathParts(`${name}Controller`),
addPathParts(name),
either.chain(from => serializePathGroup(from, name, group)),
);
}),
),
),
sequenceEither,
either.map(serialized => directory(CONTROLLERS_DIRECTORY, serialized)),
),
);
const index = pipe(
from,
addPathParts(CONTROLLERS_DIRECTORY),
either.chain(from => serializePathsIndex(from, record.keys(groupped))),
);
return combineEither(files, index, (files, index) => directory(CONTROLLERS_DIRECTORY, [...files, index]));
},
);

const serializePathsIndex = (from: Ref, pathNames: string[]): Either<Error, File> => {
if (isLeft(clientRef)) {
return clientRef;
}
const pathToClient = getRelativePath(from, clientRef.right);
const dependencies = serializeDependencies([
...array.flatten(
pathNames.map(name => {
const p = `./${name}`;
return [
serializedDependency(decapitalize(name), p),
serializedDependency(name, p),
serializedDependency(`${name}1`, p),
serializedDependency(`${name}2`, p),
];
}),
),
serializedDependency('URIS', 'fp-ts/lib/HKT'),
serializedDependency('URIS2', 'fp-ts/lib/HKT'),
serializedDependency('URIS2', 'fp-ts/lib/HKT'),
serializedDependency('HTTPClient', pathToClient),
serializedDependency('HTTPClient1', pathToClient),
serializedDependency('HTTPClient2', pathToClient),
]);
const content = `
export interface Controllers<F> {
${pathNames.map(name => `${decapitalize(name)}: ${name}<F>;`).join('\n')}
}
export interface Controllers1<F extends URIS> {
${pathNames.map(name => `${decapitalize(name)}: ${name}1<F>;`).join('\n')}
}
export interface Controllers2<F extends URIS2> {
${pathNames.map(name => `${decapitalize(name)}: ${name}2<F>;`).join('\n')}
}
export function controllers<F extends URIS2>(e: { httpClient: HTTPClient2<F> }): Controllers2<F>
export function controllers<F extends URIS>(e: { httpClient: HTTPClient1<F> }): Controllers1<F>
export function controllers<F>(e: { httpClient: HTTPClient<F> }): Controllers<F>;
export function controllers<F>(e: { httpClient: HTTPClient<F> }): Controllers<F> {
return {
${pathNames.map(name => `${decapitalize(name)}: ${decapitalize(name)}(e),`).join('\n')}
}
}
`;
return right(
file(
`${from.name}.ts`,
`
${dependencies}
${content}
`,
),
);
};

const groupPathsByTag = (pathsObject: PathsObject): Dictionary<Dictionary<PathItemObject>> => {
const keys = Object.keys(pathsObject);
const result: Record<string, PathsObject> = {};
for (const key of keys) {
const path = pathsObject[key];
const tag = pipe(
serializePathItemObjectTags(path),
option.map(p => camelize(p, false)),
option.getOrElse(() => 'Unknown'),
option.map(p => getControllerName(camelize(p, false))),
option.getOrElse(() => getControllerName('Unknown')),
);
result[tag] = {
...(result[tag] || {}),
Expand All @@ -46,3 +111,4 @@ const groupPathsByTag = (pathsObject: PathsObject): Dictionary<Dictionary<PathIt
}
return result;
};

Loading

0 comments on commit 256dc87

Please sign in to comment.