Skip to content

Commit

Permalink
feat: add fastify-openapi-glue plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobinu committed Oct 30, 2024
1 parent 7b6a960 commit c8e04c8
Show file tree
Hide file tree
Showing 9 changed files with 1,680 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/gorgeous-cups-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': minor
---

feat: add fastify-openapi-glue plugin
18 changes: 18 additions & 0 deletions packages/openapi-ts/src/plugins/fastify-openapi-glue/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { DefineConfig, PluginConfig } from '../types';
import { handler } from './plugin';
import type { Config } from './types';

export const defaultConfig: PluginConfig<Config> = {
_handler: handler,
_handlerLegacy: () => {},
name: 'fastify-openapi-glue',
output: 'fastify-openapi-glue',
};

/**
* Type helper for fastify-openapi-glue plugin, returns {@link PluginConfig} object
*/
export const defineConfig: DefineConfig<Config> = (config) => ({
...defaultConfig,
...config,
});
2 changes: 2 additions & 0 deletions packages/openapi-ts/src/plugins/fastify-openapi-glue/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { defaultConfig, defineConfig } from './config';
export type { Config } from './types';
178 changes: 178 additions & 0 deletions packages/openapi-ts/src/plugins/fastify-openapi-glue/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import type ts from 'typescript';

import { compiler, type Property } from '../../compiler';
import type { IRContext } from '../../ir/context';
import type {
IROperationObject,
IRParameterObject,
IRPathItemObject,
IRPathsObject,
} from '../../ir/ir';
import { irParametersToIrSchema } from '../../ir/schema';
import type { PluginHandler } from '../types';
import {
componentsToType,
schemaToType,
type SchemaToTypeOptions,
} from '../utils/types';
import type { Config } from './types';

const FILE_ID = 'index';
const ROUTE_HANDLER_NAME = 'RouteHandler';
const OPERATIONS_IDENTIFIER = 'FastifyOpenapiGlueRouteHandlers';
const ROUTE_PROPERTY_NAME = {
BODY: 'Body',
HEADER: 'Headers',
PATH: 'Params',
QUERY: 'Querystring',
RESPONSE: 'Reply',
};

const parameterToProperty = ({
options,
parameter,
name,
}: {
name: string;
options: SchemaToTypeOptions;
parameter: Record<string, IRParameterObject>;
}): Property => {
const schema = irParametersToIrSchema({
parameters: parameter,
});
return {
isRequired: !!schema.required,
name,
type: schemaToType({ options, schema }),
};
};

const operationToProperty = ({
operation,
options,
}: {
operation: IROperationObject;
options: SchemaToTypeOptions;
}): Property => {
const operationProperties: Array<Property> = [];

if (operation.body) {
operationProperties.push({
isRequired: operation.body.required,
name: ROUTE_PROPERTY_NAME.BODY,
type: schemaToType({
options,
schema: operation.body.schema,
}),
});
}

if (operation.parameters) {
if (operation.parameters.header) {
operationProperties.push(
parameterToProperty({
name: ROUTE_PROPERTY_NAME.HEADER,
options,
parameter: operation.parameters.header,
}),
);
}

if (operation.parameters.query) {
operationProperties.push(
parameterToProperty({
name: ROUTE_PROPERTY_NAME.QUERY,
options,
parameter: operation.parameters.query,
}),
);
}

if (operation.parameters.path) {
operationProperties.push(
parameterToProperty({
name: ROUTE_PROPERTY_NAME.PATH,
options,
parameter: operation.parameters.path,
}),
);
}
}

if (operation.responses) {
const responseProperties: Array<Property> = [];
for (const code in operation.responses) {
const response = operation.responses[code];
responseProperties.push({
name: code,
type: schemaToType({
options,
schema: response?.schema ?? {},
}),
});
}
operationProperties.push({
name: ROUTE_PROPERTY_NAME.RESPONSE,
type: compiler.typeInterfaceNode({
properties: responseProperties,
useLegacyResolution: false,
}),
});
}

const operationType = compiler.typeInterfaceNode({
properties: operationProperties,
useLegacyResolution: false,
});
const property: Property = {
name: operation.id,
type: compiler.typeNode(ROUTE_HANDLER_NAME, [operationType]),
};
return property;
};

const pathsToType = ({
context,
options,
}: {
context: IRContext;
options: SchemaToTypeOptions;
}): ts.Node => {
const operationsProperties = [];
for (const path in context.ir.paths) {
const pathItem = context.ir.paths[path as keyof IRPathsObject];
for (const method in pathItem) {
const operation = pathItem[method as keyof IRPathItemObject];
if (operation) {
const operationProperty = operationToProperty({ operation, options });
operationsProperties.push(operationProperty);
}
}
}

const identifier = context.file({ id: FILE_ID })!.identifier({
$ref: OPERATIONS_IDENTIFIER,
create: true,
namespace: 'type',
});
const paths = compiler.typeAliasDeclaration({
exportType: true,
name: identifier.name || '',
type: compiler.typeInterfaceNode({
properties: operationsProperties,
useLegacyResolution: false,
}),
});
return paths;
};

export const handler: PluginHandler<Config> = ({ context, plugin }) => {
const file = context.createFile({ id: FILE_ID, path: plugin.output });
const options: SchemaToTypeOptions = { file };
file.import({ asType: true, module: 'fastify', name: ROUTE_HANDLER_NAME });
componentsToType({
context,
options,
});
file.add(pathsToType({ context, options }));
};
11 changes: 11 additions & 0 deletions packages/openapi-ts/src/plugins/fastify-openapi-glue/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { PluginName } from '../types';

export interface Config extends PluginName<'fastify-openapi-glue'> {
/**
* Name of the generated file.
* @default 'fastify-openapi-glue'
*/
output?: string;
}

export interface UserConfig extends Omit<Config, 'output'> {}
9 changes: 8 additions & 1 deletion packages/openapi-ts/src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import {
type Config as TanStackVueQuery,
defaultConfig as tanStackVueQuery,
} from './@tanstack/vue-query';
import {
type Config as FastifyOpenapiGlue,
defaultConfig as fastifyOpenapiGlue,
} from './fastify-openapi-glue';
import type {
DefaultPluginConfigsMap,
PluginConfig,
Expand All @@ -48,7 +52,8 @@ export type UserPlugins =
| UserConfig<TanStackReactQuery>
| UserConfig<TanStackSolidQuery>
| UserConfig<TanStackSvelteQuery>
| UserConfig<TanStackVueQuery>;
| UserConfig<TanStackVueQuery>
| UserConfig<FastifyOpenapiGlue>;
// | UserConfig<Zod>

export type ClientPlugins =
Expand All @@ -60,6 +65,7 @@ export type ClientPlugins =
| PluginConfig<TanStackSolidQuery>
| PluginConfig<TanStackSvelteQuery>
| PluginConfig<TanStackVueQuery>
| PluginConfig<FastifyOpenapiGlue>
| PluginConfig<Zod>;

export const defaultPluginConfigs: DefaultPluginConfigsMap<ClientPlugins> = {
Expand All @@ -71,5 +77,6 @@ export const defaultPluginConfigs: DefaultPluginConfigsMap<ClientPlugins> = {
'@tanstack/solid-query': tanStackSolidQuery,
'@tanstack/svelte-query': tanStackSvelteQuery,
'@tanstack/vue-query': tanStackVueQuery,
'fastify-openapi-glue': fastifyOpenapiGlue,
zod,
};
1 change: 1 addition & 0 deletions packages/openapi-ts/src/plugins/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type PluginNames =
| '@tanstack/solid-query'
| '@tanstack/svelte-query'
| '@tanstack/vue-query'
| 'fastify-openapi-glue'
| 'zod';

export interface PluginName<Name extends PluginNames> {
Expand Down
Loading

0 comments on commit c8e04c8

Please sign in to comment.