Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/customizable codecs #175

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
45 changes: 45 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Decoder } from 'io-ts';
import { FSEntity } from './utils/fs';
import { taskEither } from 'fp-ts';
import { Either } from 'fp-ts/lib/Either';
import { ResolveRefContext } from './utils/ref';
import { Reader } from 'fp-ts/lib/Reader';
export interface Language<A> {
(documents: Record<string, A>): Either<unknown, FSEntity>;
}
export interface GenerateOptions<A> {
/**
* Base directory for the generation task.
* Relative paths provided in the `out` and `spec` options are resolved relative to this path.
* @default current working directory
*/
readonly cwd?: string;
/**
* Path to the output files.
* Relative paths are resolved relative to `cwd`.
*/
readonly out: string;
/**
* Path to the source schema.
* Supports local files and remote URLs, YAML and JSON formats.
* Relative paths are resolved relative to `cwd`
*/
readonly spec: string;
/**
* The `decoder` is used to parse the specification file. In most cases, one of the following decoders should be
* chosen depending on the source format:
* - `SwaggerObject`
* - `OpenapiObjectCodec`
* - `AsyncAPIObjectCodec`
*/
readonly decoder: Decoder<unknown, A>;
/**
* The `language` implements the generation of the code from the intermediate format `A` into actual
* file system objects. Most users should import one of the predefined languages:
* - `import { serialize as serializeSwagger2 } from '@devexperts/swagger-codegen-ts/dist/language/typescript/2.0'`
* - `import { serialize as serializeOpenAPI3 } from '@devexperts/swagger-codegen-ts/dist/language/typescript/3.0'`
* - `import { serialize as serializeAsyncAPI } from '@devexperts/swagger-codegen-ts/dist/language/typescript/asyncapi-2.0.0'`
*/
readonly language: Reader<ResolveRefContext, Language<A>>;
}
export declare const generate: <A>(options: GenerateOptions<A>) => taskEither.TaskEither<unknown, void>;
74 changes: 74 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const io_ts_1 = require("io-ts");
const fs_1 = require("./utils/fs");
const path = require("path");
const $RefParser = require("json-schema-ref-parser");
const pipeable_1 = require("fp-ts/lib/pipeable");
const fp_ts_1 = require("fp-ts");
const Either_1 = require("fp-ts/lib/Either");
const function_1 = require("fp-ts/lib/function");
const io_ts_2 = require("./utils/io-ts");
const sketch_121_1 = require("./parsers/sketch-121");
const log = (...args) => console.log('[SWAGGER-CODEGEN-TS]:', ...args);
const getUnsafe = fp_ts_1.either.fold(e => {
throw e;
}, function_1.identity);
exports.generate = (options) => fp_ts_1.taskEither.tryCatch(() => __awaiter(void 0, void 0, void 0, function* () {
const cwd = options.cwd || process.cwd();
const out = path.isAbsolute(options.out) ? options.out : path.resolve(cwd, options.out);
const spec = path.isAbsolute(options.spec) ? options.spec : path.resolve(cwd, options.spec);
log('Processing', spec);
const $refs = yield $RefParser.resolve(spec, {
dereference: {
circular: 'ignore',
},
parse: {
sketch: sketch_121_1.sketchParser121,
},
});
const specs = pipeable_1.pipe(Object.entries($refs.values()), fp_ts_1.array.reduce({}, (acc, [fullPath, schema]) => {
const isRoot = fullPath === spec;
const relative = path.relative(cwd, fullPath);
// skip specLike check for root because it should always be decoded with passed decoder and fail
if (!isRoot && Either_1.isLeft(specLikeCodec.decode(schema))) {
log('Unable to decode', relative, 'as spec. Treat it as an arbitrary json.');
// this is not a spec - treat as arbitrary json
return acc;
}
// use getUnsafe to fail fast if unable to decode a spec
const decoded = getUnsafe(io_ts_2.reportIfFailed(options.decoder.decode(schema)));
log('Decoded', relative);
return Object.assign(Object.assign({}, acc), { [relative]: decoded });
}));
log('Writing to', out);
const resolveRef = ($ref, decoder) => pipeable_1.pipe(fp_ts_1.either.tryCatch(() => $refs.get($ref), Either_1.toError), fp_ts_1.either.chain(resolved => io_ts_2.reportIfFailed(decoder.decode(resolved))));
yield fs_1.write(out, getUnsafe(options.language({ resolveRef })(specs)));
log('Done');
}), function_1.identity);
const specLikeCodec = io_ts_1.union([
io_ts_1.type({
swagger: io_ts_1.literal('2.0'),
}),
io_ts_1.type({
openapi: io_ts_1.union([io_ts_1.literal('3.0.0'), io_ts_1.literal('3.0.1'), io_ts_1.literal('3.0.2')]),
}),
io_ts_1.type({
asyncapi: io_ts_1.literal('2.0.0'),
}),
io_ts_1.type({
// sketch-like structure
meta: io_ts_1.type({
version: io_ts_1.literal(121),
}),
}),
]);
6 changes: 6 additions & 0 deletions dist/language/typescript/2.0/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SwaggerObject } from '../../../schema/2.0/swagger-object';
import { SerializeOptions } from '../common/utils';
import { FSEntity } from '../../../utils/fs';
import { Dictionary } from '../../../utils/types';
import { either } from 'fp-ts';
export declare const serialize: import("fp-ts/lib/Reader").Reader<import("../../../utils/ref").ResolveRefContext, (documents: Dictionary<SwaggerObject>, options?: SerializeOptions) => either.Either<Error, FSEntity>>;
11 changes: 11 additions & 0 deletions dist/language/typescript/2.0/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../common/utils");
const fs_1 = require("../../../utils/fs");
const fp_ts_1 = require("fp-ts");
const pipeable_1 = require("fp-ts/lib/pipeable");
const swagger_object_1 = require("./serializers/swagger-object");
const prettier_1 = require("prettier");
const either_utils_1 = require("@devexperts/utils/dist/adt/either.utils");
const reader_utils_1 = require("@devexperts/utils/dist/adt/reader.utils");
exports.serialize = reader_utils_1.combineReader(swagger_object_1.serializeSwaggerObject, serializeSwaggerObject => (documents, options = {}) => pipeable_1.pipe(documents, fp_ts_1.record.collect(serializeSwaggerObject), either_utils_1.sequenceEither, fp_ts_1.either.map(serialized => fs_1.map(fs_1.fragment(serialized), content => prettier_1.format(content, options.prettierConfig || utils_1.defaultPrettierConfig)))));
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Directory } from '../../../../utils/fs';
import { DefinitionsObject } from '../../../../schema/2.0/definitions-object';
import { either } from 'fp-ts';
import { Ref } from '../../../../utils/ref';
export declare const serializeDefinitions: (from: Ref<string>, definitions: DefinitionsObject) => either.Either<Error, Directory>;
20 changes: 20 additions & 0 deletions dist/language/typescript/2.0/serializers/definitions-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("../../../../utils/fs");
const schema_object_1 = require("./schema-object");
const serialized_dependency_1 = require("../../common/data/serialized-dependency");
const utils_1 = require("../../common/utils");
const pipeable_1 = require("fp-ts/lib/pipeable");
const fp_ts_1 = require("fp-ts");
const either_utils_1 = require("@devexperts/utils/dist/adt/either.utils");
const ref_1 = require("../../../../utils/ref");
exports.serializeDefinitions = (from, definitions) => pipeable_1.pipe(definitions, fp_ts_1.record.collect((name, definition) => pipeable_1.pipe(from, ref_1.addPathParts(name), fp_ts_1.either.chain(from => serializeDefinition(from, name, definition)))), either_utils_1.sequenceEither, fp_ts_1.either.map(serialized => fs_1.directory(utils_1.DEFINITIONS_DIRECTORY, serialized)));
const serializeDefinition = (from, name, definition) => pipeable_1.pipe(schema_object_1.serializeSchemaObject(from, definition), fp_ts_1.either.map(serialized => {
const dependencies = serialized_dependency_1.serializeDependencies(serialized.dependencies);
return fs_1.file(`${name}.ts`, `
${dependencies}

export type ${name} = ${serialized.type};
export const ${utils_1.getIOName(name)} = ${serialized.io};
`);
}));
5 changes: 5 additions & 0 deletions dist/language/typescript/2.0/serializers/items-object.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ItemsObject } from '../../../../schema/2.0/items-object';
import { SerializedType } from '../../common/data/serialized-type';
import { Ref } from '../../../../utils/ref';
import { either } from 'fp-ts';
export declare const serializeItemsObject: (from: Ref<string>, itemsObject: ItemsObject) => either.Either<Error, SerializedType>;
26 changes: 26 additions & 0 deletions dist/language/typescript/2.0/serializers/items-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const serialized_type_1 = require("../../common/data/serialized-type");
const pipeable_1 = require("fp-ts/lib/pipeable");
const fp_ts_1 = require("fp-ts");
const Either_1 = require("fp-ts/lib/Either");
const Option_1 = require("fp-ts/lib/Option");
exports.serializeItemsObject = (from, itemsObject) => {
switch (itemsObject.type) {
case 'array': {
return pipeable_1.pipe(exports.serializeItemsObject(from, itemsObject.items), fp_ts_1.either.map(serialized_type_1.getSerializedArrayType(Option_1.none)));
}
case 'string': {
return serialized_type_1.getSerializedStringType(from, itemsObject.format);
}
case 'number': {
return Either_1.right(serialized_type_1.SERIALIZED_NUMBER_TYPE);
}
case 'integer': {
return Either_1.right(serialized_type_1.SERIALIZED_INTEGER_TYPE);
}
case 'boolean': {
return Either_1.right(serialized_type_1.SERIALIZED_BOOLEAN_TYPE);
}
}
};
12 changes: 12 additions & 0 deletions dist/language/typescript/2.0/serializers/operation-object.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { OperationObject } from '../../../../schema/2.0/operation-object';
import { SerializedType } from '../../common/data/serialized-type';
import { HTTPMethod } from '../../common/utils';
import { Either } from 'fp-ts/lib/Either';
import { ResolveRefContext, Ref } from '../../../../utils/ref';
import { QueryParameterObject } from '../../../../schema/2.0/parameter-object';
import { PathItemObject } from '../../../../schema/2.0/path-item-object';
import { Kind } from '../../../../utils/types';
import { SerializedFragment } from '../../common/data/serialized-fragment';
export declare const serializeOperationObject: import("fp-ts/lib/Reader").Reader<ResolveRefContext, (from: Ref<string>, url: string, method: HTTPMethod, kind: Kind, operation: OperationObject, pathItem: PathItemObject) => Either<Error, SerializedType>>;
export declare const getCollectionSeparator: (format: "csv" | "ssv" | "tsv" | "pipes") => string;
export declare const serializeQueryParameterObject: (from: Ref<string>, parameter: QueryParameterObject, serialized: SerializedType, target: string) => Either<Error, SerializedFragment>;
Loading