Skip to content

Commit b1e9563

Browse files
author
Morgan Saville
committed
Add flag for generating server stubs
1 parent be7d53d commit b1e9563

22 files changed

+341
-33
lines changed

README.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,24 @@ $ openapi --help
5050
Usage: openapi [options]
5151
5252
Options:
53-
-V, --version output the version number
54-
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
55-
-o, --output <value> Output directory (required)
56-
-c, --client <value> HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch")
57-
--name <value> Custom client class name
58-
--useOptions Use options instead of arguments
59-
--useUnionTypes Use union types instead of enums
60-
--exportCore <value> Write core files to disk (default: true)
61-
--exportServices <value> Write services to disk (default: true)
62-
--exportModels <value> Write models to disk (default: true)
63-
--exportSchemas <value> Write schemas to disk (default: false)
64-
--indent <value> Indentation options [4, 2, tab] (default: "4")
65-
--postfixServices Service name postfix (default: "Service")
66-
--postfixModels Model name postfix
67-
--request <value> Path to custom request file
68-
-h, --help display help for command
53+
-V, --version output the version number
54+
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
55+
-o, --output <value> Output directory (required)
56+
-c, --client <value> HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch")
57+
--name <value> Custom client class name
58+
--useOptions Use options instead of arguments
59+
--useUnionTypes Use union types instead of enums
60+
--exportCore <value> Write core files to disk (default: true)
61+
--exportServices <value> Write services to disk (default: true)
62+
--exportModels <value> Write models to disk (default: true)
63+
--exportSchemas <value> Write schemas to disk (default: false)
64+
--exportServerStubs <value> Write server stubs to disk (default: false)
65+
--indent <value> Indentation options [4, 2, tabs] (default: "4")
66+
--postfixServices <value> Service name postfix (default: "Service")
67+
--postfixServerStubs <value> Server stub name postfix (default: "Server")
68+
--postfixModels <value> Model name postfix
69+
--request <value> Path to custom request file
70+
-h, --help display help for command
6971
7072
Examples
7173
$ openapi --input ./spec.json --output ./generated

bin/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ const params = program
2020
.option('--exportServices <value>', 'Write services to disk', true)
2121
.option('--exportModels <value>', 'Write models to disk', true)
2222
.option('--exportSchemas <value>', 'Write schemas to disk', false)
23+
.option('--exportServerStubs <value>', 'Write server stubs to disk', false)
2324
.option('--indent <value>', 'Indentation options [4, 2, tabs]', '4')
2425
.option('--postfixServices <value>', 'Service name postfix', 'Service')
26+
.option('--postfixServerStubs <value>', 'Server stub name postfix', 'Server')
2527
.option('--postfixModels <value>', 'Model name postfix')
2628
.option('--request <value>', 'Path to custom request file')
2729
.parse(process.argv)
@@ -41,9 +43,11 @@ if (OpenAPI) {
4143
exportServices: JSON.parse(params.exportServices) === true,
4244
exportModels: JSON.parse(params.exportModels) === true,
4345
exportSchemas: JSON.parse(params.exportSchemas) === true,
46+
exportServerStubs: JSON.parse(params.exportServerStubs) === true,
4447
indent: params.indent,
4548
postfixServices: params.postfixServices,
4649
postfixModels: params.postfixModels,
50+
postfixServerStubs: params.postfixServerStubs,
4751
request: params.request,
4852
})
4953
.then(() => {

package-lock.json

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ export type Options = {
2323
exportServices?: boolean;
2424
exportModels?: boolean;
2525
exportSchemas?: boolean;
26+
exportServerStubs?: boolean;
2627
indent?: Indent;
2728
postfixServices?: string;
2829
postfixModels?: string;
30+
postfixServerStubs?: string;
2931
request?: string;
3032
write?: boolean;
3133
};
@@ -44,9 +46,11 @@ export type Options = {
4446
* @param exportServices Generate services
4547
* @param exportModels Generate models
4648
* @param exportSchemas Generate schemas
49+
* @param exportServerStubs Generate server stubs
4750
* @param indent Indentation options (4, 2 or tab)
4851
* @param postfixServices Service name postfix
4952
* @param postfixModels Model name postfix
53+
* @param postfixServerStubs Server stub name postfix
5054
* @param request Path to custom request file
5155
* @param write Write the files to disk (true or false)
5256
*/
@@ -61,9 +65,11 @@ export const generate = async ({
6165
exportServices = true,
6266
exportModels = true,
6367
exportSchemas = false,
68+
exportServerStubs = false,
6469
indent = Indent.SPACE_4,
6570
postfixServices = 'Service',
6671
postfixModels = '',
72+
postfixServerStubs = 'Server',
6773
request,
6874
write = true,
6975
}: Options): Promise<void> => {
@@ -91,9 +97,11 @@ export const generate = async ({
9197
exportServices,
9298
exportModels,
9399
exportSchemas,
100+
exportServerStubs,
94101
indent,
95102
postfixServices,
96103
postfixModels,
104+
postfixServerStubs,
97105
clientName,
98106
request
99107
);
@@ -115,9 +123,11 @@ export const generate = async ({
115123
exportServices,
116124
exportModels,
117125
exportSchemas,
126+
exportServerStubs,
118127
indent,
119128
postfixServices,
120129
postfixModels,
130+
postfixServerStubs,
121131
clientName,
122132
request
123133
);

src/openApi/v2/parser/getOperationParameterName.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@ import camelCase from 'camelcase';
22

33
import { reservedWords } from '../../../utils/reservedWords';
44

5+
const CHAR_REPLACEMENTS = {
6+
">": "gt",
7+
"<": "lt",
8+
} as const;
9+
510
/**
611
* Replaces any invalid characters from a parameter name.
712
* For example: 'filter.someProperty' becomes 'filterSomeProperty'.
813
*/
914
export const getOperationParameterName = (value: string): string => {
10-
const clean = value
11-
.replace(/^[^a-zA-Z]+/g, '')
15+
let clean = value;
16+
for (const entry of Object.entries(CHAR_REPLACEMENTS)) {
17+
const [key, value] = entry;
18+
clean = clean.replace(new RegExp(`^${key}$`, 'g'), value);
19+
clean = clean.replace(new RegExp(`^${key}`, 'g'), `${value}-`);
20+
clean = clean.replace(new RegExp(`${key}$`, 'g'), `-${value}`);
21+
clean = clean.replace(new RegExp(key, 'g'), `-${value}-`);
22+
}
23+
24+
clean = clean.replace(/^[^a-zA-Z]+/g, '')
1225
.replace(/[^\w\-]+/g, '-')
1326
.trim();
1427
return camelCase(clean).replace(reservedWords, '_$1');

src/openApi/v3/parser/getOperationParameterName.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@ import camelCase from 'camelcase';
22

33
import { reservedWords } from '../../../utils/reservedWords';
44

5+
const CHAR_REPLACEMENTS = {
6+
">": "gt",
7+
"<": "lt",
8+
} as const;
9+
510
/**
611
* Replaces any invalid characters from a parameter name.
712
* For example: 'filter.someProperty' becomes 'filterSomeProperty'.
813
*/
914
export const getOperationParameterName = (value: string): string => {
10-
const clean = value
15+
let clean = value;
16+
for (const entry of Object.entries(CHAR_REPLACEMENTS)) {
17+
const [key, value] = entry;
18+
clean = clean.replace(new RegExp(`^${key}$`, 'g'), value);
19+
clean = clean.replace(new RegExp(`^${key}`, 'g'), `${value}-`);
20+
clean = clean.replace(new RegExp(`${key}$`, 'g'), `-${value}`);
21+
clean = clean.replace(new RegExp(key, 'g'), `-${value}-`);
22+
}
23+
24+
clean = clean
1125
.replace(/^[^a-zA-Z]+/g, '')
1226
.replace('[]', 'Array')
1327
.replace(/[^\w\-]+/g, '-')

src/templates/exportServerStub.hbs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
{{>header}}
2+
3+
import { type Request, type Response, Router } from 'express';
4+
5+
{{#if imports}}
6+
{{#each imports}}
7+
import type { {{{this}}} } from '../models/{{{this}}}';
8+
{{/each}}
9+
{{/if}}
10+
11+
export interface {{{name}}}{{{@root.postfix}}}Impl {
12+
{{#each operations}}
13+
/**
14+
{{#if deprecated}}
15+
* @deprecated
16+
{{/if}}
17+
{{#if summary}}
18+
* {{{escapeComment summary}}}
19+
{{/if}}
20+
{{#if description}}
21+
* {{{escapeComment description}}}
22+
{{/if}}
23+
{{#if parameters}}
24+
{{#each parameters}}
25+
* @param {{{name}}} {{#if description}}{{{escapeComment description}}}{{/if}}
26+
{{/each}}
27+
{{/if}}
28+
{{#each results}}
29+
* @returns {{{type}}} {{#if description}}{{{escapeComment description}}}{{/if}}
30+
{{/each}}
31+
*/
32+
{{{name}}}?: ({{>serverParameters}}) => Promise<{{>resultWithCode}}>;
33+
{{/each}}
34+
}
35+
36+
export class {{{name}}}{{{@root.postfix}}} {
37+
impl: {{{name}}}{{{@root.postfix}}}Impl
38+
router: Router;
39+
40+
constructor(impl: {{{name}}}{{{@root.postfix}}}Impl) {
41+
this.impl = impl;
42+
this.router = Router();
43+
{{#each operations}}
44+
this.router.
45+
{{~#equals method "GET"~}}
46+
get
47+
{{~else equals method "HEAD"~}}
48+
head
49+
{{~else equals method "POST"~}}
50+
post
51+
{{~else equals method "PUT"~}}
52+
put
53+
{{~else equals method "DELETE"~}}
54+
delete
55+
{{~else equals method "OPTIONS"~}}
56+
options
57+
{{~else equals method "TRACE"~}}
58+
trace
59+
{{~else equals method "PATCH"~}}
60+
patch
61+
{{~/equals~}}
62+
(toExpressRoute('{{{path}}}'), this.{{{name}}}.bind(this));
63+
{{/each}}
64+
}
65+
66+
{{#each operations}}
67+
public async {{{name}}}(req: Request, res: Response): Promise<void> {
68+
{{#if parametersPath}}
69+
{{#each parametersPath}}
70+
{{>getParamFromReq}}
71+
72+
{{/each}}
73+
{{/if}}
74+
{{#if parametersCookie}}
75+
{{#each parametersCookie}}
76+
{{>getParamFromReq}}
77+
78+
{{/each}}
79+
{{/if}}
80+
{{#if parametersHeader}}
81+
{{#each parametersHeader}}
82+
{{>getParamFromReq}}
83+
84+
{{/each}}
85+
{{/if}}
86+
{{#if parametersQuery}}
87+
{{#each parametersQuery}}
88+
{{>getParamFromReq}}
89+
90+
{{/each}}
91+
{{/if}}
92+
{{#if parametersForm}}
93+
{{#each parametersForm}}
94+
{{>getParamFromReq}}
95+
96+
{{/each}}
97+
{{/if}}
98+
{{#if parametersBody}}
99+
{{#with parametersBody}}
100+
const {{{name}}} = req.body as {{>type}};
101+
{{/with}}
102+
{{/if}}
103+
try {
104+
if (this.impl.{{{name}}} === undefined) {
105+
throw new Error('Not implemented');
106+
} else {
107+
const { code, data } = await this.impl.{{{name}}}(
108+
{{#if parameters}}
109+
{{#each parameters}}
110+
{{{name}}},
111+
{{/each}}
112+
{{/if}}
113+
);
114+
res.status(code).json(data);
115+
}
116+
} catch (e) {
117+
console.error(e);
118+
res.status(500).send(`Error: ${e}`);
119+
}
120+
}
121+
122+
{{/each}}
123+
124+
}
125+
126+
function toExpressRoute(path: string): string {
127+
return path.replace(/{([^}]*)}/g, ':$1');
128+
}

src/templates/index.hbs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,11 @@ export { {{{name}}}{{{@root.postfixServices}}} } from './services/{{{name}}}{{{@
4545
{{/each}}
4646
{{/if}}
4747
{{/if}}
48+
{{#if @root.exportServerStubs}}
49+
{{#if services}}
50+
51+
{{#each services}}
52+
export { {{{name}}}{{{@root.postfixServerStubs}}} } from './server_stubs/{{{name}}}{{{@root.postfixServerStubs}}}';
53+
{{/each}}
54+
{{/if}}
55+
{{/if}}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const {{{name}}} = ({{#unless isRequired}}req.
2+
{{~#equals in "path"~}}
3+
params
4+
{{~else equals in "cookie"~}}
5+
cookies
6+
{{~else equals in "header"~}}
7+
headers
8+
{{~else equals in "query"~}}
9+
query
10+
{{~else equals in "formData"~}}
11+
body
12+
{{~else equals in "body"~}}
13+
body
14+
{{~/equals~}}
15+
['{{{prop}}}'] ? {{/unless}}{{#equals type "number"}}+({{/equals}}req.
16+
{{~#equals in "path"~}}
17+
params
18+
{{~else equals in "cookie"~}}
19+
cookies
20+
{{~else equals in "header"~}}
21+
headers
22+
{{~else equals in "query"~}}
23+
query
24+
{{~else equals in "formData"~}}
25+
body
26+
{{~else equals in "body"~}}
27+
body
28+
{{~/equals~}}
29+
['{{{prop}}}']{{#equals type "number"}} as string){{else equals type "boolean"}} === "true"{{else}} as {{>type}}{{/equals}}{{#unless isRequired}} : undefined{{/unless}});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{{~#if results~}}
2+
{{#each results}}{ code: {{{code}}}, data: {{>type}} }{{#unless @last}} | {{/unless}}{{/each}}
3+
{{~else~}}
4+
void
5+
{{~/if~}}

0 commit comments

Comments
 (0)