Skip to content
Closed
20 changes: 20 additions & 0 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: unittest

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- name: Cache Modules
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
- run: npm run install
- run: npm run test

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ test/e2e/generated
samples/generated
samples/swagger-codegen-cli-v2.jar
samples/swagger-codegen-cli-v3.jar
.env
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ $ openapi --help
--useOptions Use options instead of arguments
--useUnionTypes Use union types instead of enums
--exportCore <value> Write core files to disk (default: true)
--exportServices <value> Write services to disk (default: true)
--exportServices <value> Write services to disk [true, false, regexp] (default: true)
--exportModels <value> Write models to disk (default: true)
--exportSchemas <value> Write schemas to disk (default: false)
--indent <value> Indentation options [4, 2, tab] (default: "4")
Expand Down
8 changes: 7 additions & 1 deletion bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const params = program
const OpenAPI = require(path.resolve(__dirname, '../dist/index.js'));

if (OpenAPI) {
let exportServices;
try {
exportServices = JSON.parse(params.exportServices) === true;
} catch (error) {
exportServices = params.exportServices;
}
OpenAPI.generate({
input: params.input,
output: params.output,
Expand All @@ -38,7 +44,7 @@ if (OpenAPI) {
useOptions: params.useOptions,
useUnionTypes: params.useUnionTypes,
exportCore: JSON.parse(params.exportCore) === true,
exportServices: JSON.parse(params.exportServices) === true,
exportServices,
exportModels: JSON.parse(params.exportModels) === true,
exportSchemas: JSON.parse(params.exportSchemas) === true,
indent: params.indent,
Expand Down
14 changes: 14 additions & 0 deletions bin/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ describe('bin', () => {
expect(result.stderr.toString()).toBe('');
});

it('it should support regexp in exportSchemas', async () => {
const result = crossSpawn.sync('node', [
'./bin/index.js',
'--input',
'./test/spec/v3.json',
'--output',
'./test/generated/bin',
'--exportServices',
'^(Simple|Types)',
]);
expect(result.stdout.toString()).toBe('');
expect(result.stderr.toString()).toBe('');
});

it('it should throw error without params', async () => {
const result = crossSpawn.sync('node', ['./bin/index.js']);
expect(result.stdout.toString()).toBe('');
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "openapi-typescript-codegen",
"version": "0.27.0",
"name": "@canoapbc/openapi-typescript-codegen",
"version": "0.27.2",
"description": "Library that generates Typescript clients based on the OpenAPI specification.",
"author": "Ferdi Koomen",
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",
"homepage": "https://github.com/CanoaPBC/openapi-typescript-codegen",
"repository": {
"type": "git",
"url": "git+https://github.com/ferdikoomen/openapi-typescript-codegen.git"
"url": "git+https://github.com/CanoaPBC/openapi-typescript-codegen.git"
},
"bugs": {
"url": "https://github.com/ferdikoomen/openapi-typescript-codegen/issues"
Expand Down
12 changes: 11 additions & 1 deletion src/client/interfaces/Model.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ import type { Schema } from './Schema';

export interface Model extends Schema {
name: string;
export: 'reference' | 'generic' | 'enum' | 'array' | 'dictionary' | 'interface' | 'one-of' | 'any-of' | 'all-of';
export:
| 'reference'
| 'generic'
| 'enum'
| 'array'
| 'dictionary'
| 'interface'
| 'one-of'
| 'any-of'
| 'all-of'
| 'const';
type: string;
base: string;
template: string | null;
Expand Down
38 changes: 13 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type Options = {
useOptions?: boolean;
useUnionTypes?: boolean;
exportCore?: boolean;
exportServices?: boolean;
exportServices?: boolean | string;
exportModels?: boolean;
exportSchemas?: boolean;
indent?: Indent;
Expand Down Expand Up @@ -75,35 +75,24 @@ export const generate = async ({
useOptions,
});

let parser: typeof parseV2 | typeof parseV3;

switch (openApiVersion) {
case OpenApiVersion.V2: {
const client = parseV2(openApi);
const clientFinal = postProcessClient(client);
if (!write) break;
await writeClient(
clientFinal,
templates,
output,
httpClient,
useOptions,
useUnionTypes,
exportCore,
exportServices,
exportModels,
exportSchemas,
indent,
postfixServices,
postfixModels,
clientName,
request
);
parser = parseV2;
break;
}

case OpenApiVersion.V3: {
const client = parseV3(openApi);
const clientFinal = postProcessClient(client);
if (!write) break;
parser = parseV3;
break;
}
}

if (parser) {
const client = parser(openApi);
const clientFinal = postProcessClient(client);
if (write) {
await writeClient(
clientFinal,
templates,
Expand All @@ -121,7 +110,6 @@ export const generate = async ({
clientName,
request
);
break;
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions src/openApi/v2/parser/getOperationResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const getOperationResponse = (
code: responseCode,
description: response.description || null,
export: 'generic',
type: 'any',
base: 'any',
type: responseCode !== 204 ? 'any' : 'void',
base: responseCode !== 204 ? 'any' : 'void',
template: null,
link: null,
isDefinition: false,
Expand Down
26 changes: 2 additions & 24 deletions src/openApi/v2/parser/getOperationResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,14 @@ const areEqual = (a: Model, b: Model): boolean => {
export const getOperationResults = (operationResponses: OperationResponse[]): OperationResponse[] => {
const operationResults: OperationResponse[] = [];

// Filter out success response codes, but skip "204 No Content"
// Filter out success response codes
operationResponses.forEach(operationResponse => {
const { code } = operationResponse;
if (code && code !== 204 && code >= 200 && code < 300) {
if (code && code >= 200 && code < 300) {
operationResults.push(operationResponse);
}
});

if (!operationResults.length) {
operationResults.push({
in: 'response',
name: '',
code: 200,
description: '',
export: 'generic',
type: 'void',
base: 'void',
template: null,
link: null,
isDefinition: false,
isReadOnly: false,
isRequired: false,
isNullable: false,
imports: [],
enum: [],
enums: [],
properties: [],
});
}

return operationResults.filter((operationResult, index, arr) => {
return (
arr.findIndex(item => {
Expand Down
1 change: 1 addition & 0 deletions src/openApi/v3/interfaces/OpenApiSchema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface OpenApiSchema extends OpenApiReference, WithEnumExtension {
required?: string[];
enum?: (string | number)[];
type?: string | string[];
const?: string | number | null;
allOf?: OpenApiSchema[];
oneOf?: OpenApiSchema[];
anyOf?: OpenApiSchema[];
Expand Down
92 changes: 92 additions & 0 deletions src/openApi/v3/parser/getModel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { reservedWords } from '../../../utils/reservedWords';
import { getModel } from './getModel';
import { getType } from './getType';

const openApi = {
openapi: '3.0',
info: {
title: 'dummy',
version: '1.0',
},
paths: {},
servers: [
{
url: 'https://localhost:8080/api',
},
],
components: {
schemas: {
Enum1: {
enum: ['Bird', 'Dog'],
type: 'string',
},
ConstValue: {
type: 'string',
const: 'ConstValue',
},
CompositionWithAnyOfAndNull: {
description:
"This is a model with one property with a 'any of' relationship where the options are not $ref",
type: 'object',
properties: {
propA: {
anyOf: [
{
items: {
anyOf: [
{
$ref: '#/components/schemas/Enum1',
},
{
$ref: '#/components/schemas/ConstValue',
},
],
},
type: 'array',
},
{
type: 'null',
},
],
},
},
},
CompositionWithAny: {
description:
"This is a model with one property with a 'any of' relationship where the options are not $ref",
type: 'object',
properties: {
propA: {
anyOf: [
{
$ref: '#/components/schemas/Enum1',
},
{
$ref: '#/components/schemas/ConstValue',
},
{
type: 'null',
},
],
},
},
},
},
},
};

describe('getModel', () => {
it('Parses any of', () => {
const definition = openApi.components.schemas.CompositionWithAnyOfAndNull;
const definitionType = getType('CompositionWithAnyOfAndNull');
const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1'));
expect(model.properties[0].properties.length).toBe(2);
});

it('Parses any of 2', () => {
const definition = openApi.components.schemas.CompositionWithAny;
const definitionType = getType('CompositionWithAny');
const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1'));
expect(model.properties[0].properties.length).toBe(3);
});
});
11 changes: 11 additions & 0 deletions src/openApi/v3/parser/getModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export const getModel = (
model.imports.push(...arrayItems.imports);
model.default = getModelDefault(definition, model);
return model;
} else if (definition.items.anyOf) {
return getModel(openApi, definition.items);
} else {
const arrayItems = getModel(openApi, definition.items);
model.export = 'array';
Expand Down Expand Up @@ -179,6 +181,15 @@ export const getModel = (
}
}

if (definition.const !== undefined) {
model.export = 'const';
const definitionConst = definition.const;
const modelConst = typeof definitionConst === 'string' ? `"${definitionConst}"` : `${definitionConst}`;
model.type = modelConst;
model.base = modelConst;
return model;
}

// If the schema has a type than it can be a basic or generic type.
if (definition.type) {
const definitionType = getType(definition.type, definition.format);
Expand Down
4 changes: 2 additions & 2 deletions src/openApi/v3/parser/getOperationResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const getOperationResponse = (
code: responseCode,
description: response.description || null,
export: 'generic',
type: 'any',
base: 'any',
type: responseCode !== 204 ? 'any' : 'void',
base: responseCode !== 204 ? 'any' : 'void',
template: null,
link: null,
isDefinition: false,
Expand Down
Loading