Skip to content

Commit c8e04c8

Browse files
committed
feat: add fastify-openapi-glue plugin
1 parent 7b6a960 commit c8e04c8

File tree

9 files changed

+1680
-1
lines changed

9 files changed

+1680
-1
lines changed

.changeset/gorgeous-cups-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': minor
3+
---
4+
5+
feat: add fastify-openapi-glue plugin
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { DefineConfig, PluginConfig } from '../types';
2+
import { handler } from './plugin';
3+
import type { Config } from './types';
4+
5+
export const defaultConfig: PluginConfig<Config> = {
6+
_handler: handler,
7+
_handlerLegacy: () => {},
8+
name: 'fastify-openapi-glue',
9+
output: 'fastify-openapi-glue',
10+
};
11+
12+
/**
13+
* Type helper for fastify-openapi-glue plugin, returns {@link PluginConfig} object
14+
*/
15+
export const defineConfig: DefineConfig<Config> = (config) => ({
16+
...defaultConfig,
17+
...config,
18+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { defaultConfig, defineConfig } from './config';
2+
export type { Config } from './types';
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import type ts from 'typescript';
2+
3+
import { compiler, type Property } from '../../compiler';
4+
import type { IRContext } from '../../ir/context';
5+
import type {
6+
IROperationObject,
7+
IRParameterObject,
8+
IRPathItemObject,
9+
IRPathsObject,
10+
} from '../../ir/ir';
11+
import { irParametersToIrSchema } from '../../ir/schema';
12+
import type { PluginHandler } from '../types';
13+
import {
14+
componentsToType,
15+
schemaToType,
16+
type SchemaToTypeOptions,
17+
} from '../utils/types';
18+
import type { Config } from './types';
19+
20+
const FILE_ID = 'index';
21+
const ROUTE_HANDLER_NAME = 'RouteHandler';
22+
const OPERATIONS_IDENTIFIER = 'FastifyOpenapiGlueRouteHandlers';
23+
const ROUTE_PROPERTY_NAME = {
24+
BODY: 'Body',
25+
HEADER: 'Headers',
26+
PATH: 'Params',
27+
QUERY: 'Querystring',
28+
RESPONSE: 'Reply',
29+
};
30+
31+
const parameterToProperty = ({
32+
options,
33+
parameter,
34+
name,
35+
}: {
36+
name: string;
37+
options: SchemaToTypeOptions;
38+
parameter: Record<string, IRParameterObject>;
39+
}): Property => {
40+
const schema = irParametersToIrSchema({
41+
parameters: parameter,
42+
});
43+
return {
44+
isRequired: !!schema.required,
45+
name,
46+
type: schemaToType({ options, schema }),
47+
};
48+
};
49+
50+
const operationToProperty = ({
51+
operation,
52+
options,
53+
}: {
54+
operation: IROperationObject;
55+
options: SchemaToTypeOptions;
56+
}): Property => {
57+
const operationProperties: Array<Property> = [];
58+
59+
if (operation.body) {
60+
operationProperties.push({
61+
isRequired: operation.body.required,
62+
name: ROUTE_PROPERTY_NAME.BODY,
63+
type: schemaToType({
64+
options,
65+
schema: operation.body.schema,
66+
}),
67+
});
68+
}
69+
70+
if (operation.parameters) {
71+
if (operation.parameters.header) {
72+
operationProperties.push(
73+
parameterToProperty({
74+
name: ROUTE_PROPERTY_NAME.HEADER,
75+
options,
76+
parameter: operation.parameters.header,
77+
}),
78+
);
79+
}
80+
81+
if (operation.parameters.query) {
82+
operationProperties.push(
83+
parameterToProperty({
84+
name: ROUTE_PROPERTY_NAME.QUERY,
85+
options,
86+
parameter: operation.parameters.query,
87+
}),
88+
);
89+
}
90+
91+
if (operation.parameters.path) {
92+
operationProperties.push(
93+
parameterToProperty({
94+
name: ROUTE_PROPERTY_NAME.PATH,
95+
options,
96+
parameter: operation.parameters.path,
97+
}),
98+
);
99+
}
100+
}
101+
102+
if (operation.responses) {
103+
const responseProperties: Array<Property> = [];
104+
for (const code in operation.responses) {
105+
const response = operation.responses[code];
106+
responseProperties.push({
107+
name: code,
108+
type: schemaToType({
109+
options,
110+
schema: response?.schema ?? {},
111+
}),
112+
});
113+
}
114+
operationProperties.push({
115+
name: ROUTE_PROPERTY_NAME.RESPONSE,
116+
type: compiler.typeInterfaceNode({
117+
properties: responseProperties,
118+
useLegacyResolution: false,
119+
}),
120+
});
121+
}
122+
123+
const operationType = compiler.typeInterfaceNode({
124+
properties: operationProperties,
125+
useLegacyResolution: false,
126+
});
127+
const property: Property = {
128+
name: operation.id,
129+
type: compiler.typeNode(ROUTE_HANDLER_NAME, [operationType]),
130+
};
131+
return property;
132+
};
133+
134+
const pathsToType = ({
135+
context,
136+
options,
137+
}: {
138+
context: IRContext;
139+
options: SchemaToTypeOptions;
140+
}): ts.Node => {
141+
const operationsProperties = [];
142+
for (const path in context.ir.paths) {
143+
const pathItem = context.ir.paths[path as keyof IRPathsObject];
144+
for (const method in pathItem) {
145+
const operation = pathItem[method as keyof IRPathItemObject];
146+
if (operation) {
147+
const operationProperty = operationToProperty({ operation, options });
148+
operationsProperties.push(operationProperty);
149+
}
150+
}
151+
}
152+
153+
const identifier = context.file({ id: FILE_ID })!.identifier({
154+
$ref: OPERATIONS_IDENTIFIER,
155+
create: true,
156+
namespace: 'type',
157+
});
158+
const paths = compiler.typeAliasDeclaration({
159+
exportType: true,
160+
name: identifier.name || '',
161+
type: compiler.typeInterfaceNode({
162+
properties: operationsProperties,
163+
useLegacyResolution: false,
164+
}),
165+
});
166+
return paths;
167+
};
168+
169+
export const handler: PluginHandler<Config> = ({ context, plugin }) => {
170+
const file = context.createFile({ id: FILE_ID, path: plugin.output });
171+
const options: SchemaToTypeOptions = { file };
172+
file.import({ asType: true, module: 'fastify', name: ROUTE_HANDLER_NAME });
173+
componentsToType({
174+
context,
175+
options,
176+
});
177+
file.add(pathsToType({ context, options }));
178+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { PluginName } from '../types';
2+
3+
export interface Config extends PluginName<'fastify-openapi-glue'> {
4+
/**
5+
* Name of the generated file.
6+
* @default 'fastify-openapi-glue'
7+
*/
8+
output?: string;
9+
}
10+
11+
export interface UserConfig extends Omit<Config, 'output'> {}

packages/openapi-ts/src/plugins/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ import {
3030
type Config as TanStackVueQuery,
3131
defaultConfig as tanStackVueQuery,
3232
} from './@tanstack/vue-query';
33+
import {
34+
type Config as FastifyOpenapiGlue,
35+
defaultConfig as fastifyOpenapiGlue,
36+
} from './fastify-openapi-glue';
3337
import type {
3438
DefaultPluginConfigsMap,
3539
PluginConfig,
@@ -48,7 +52,8 @@ export type UserPlugins =
4852
| UserConfig<TanStackReactQuery>
4953
| UserConfig<TanStackSolidQuery>
5054
| UserConfig<TanStackSvelteQuery>
51-
| UserConfig<TanStackVueQuery>;
55+
| UserConfig<TanStackVueQuery>
56+
| UserConfig<FastifyOpenapiGlue>;
5257
// | UserConfig<Zod>
5358

5459
export type ClientPlugins =
@@ -60,6 +65,7 @@ export type ClientPlugins =
6065
| PluginConfig<TanStackSolidQuery>
6166
| PluginConfig<TanStackSvelteQuery>
6267
| PluginConfig<TanStackVueQuery>
68+
| PluginConfig<FastifyOpenapiGlue>
6369
| PluginConfig<Zod>;
6470

6571
export const defaultPluginConfigs: DefaultPluginConfigsMap<ClientPlugins> = {
@@ -71,5 +77,6 @@ export const defaultPluginConfigs: DefaultPluginConfigsMap<ClientPlugins> = {
7177
'@tanstack/solid-query': tanStackSolidQuery,
7278
'@tanstack/svelte-query': tanStackSvelteQuery,
7379
'@tanstack/vue-query': tanStackVueQuery,
80+
'fastify-openapi-glue': fastifyOpenapiGlue,
7481
zod,
7582
};

packages/openapi-ts/src/plugins/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export type PluginNames =
3232
| '@tanstack/solid-query'
3333
| '@tanstack/svelte-query'
3434
| '@tanstack/vue-query'
35+
| 'fastify-openapi-glue'
3536
| 'zod';
3637

3738
export interface PluginName<Name extends PluginNames> {

0 commit comments

Comments
 (0)