diff --git a/Directory.Build.props b/Directory.Build.props index cd02350..b46188a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - 3.8.0 - 3.7.0 + 3.9.0 + 3.8.0 12.0 enable enable diff --git a/ReleaseNotes.md b/ReleaseNotes.md index efcf1de..3eacdae 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -2,6 +2,10 @@ These are the NuGet package releases. See also [npm Release Notes](ReleaseNotesNpm.md). +## 3.9.0 + +* Add ability to configure route specific `bodyLimit` in fastify plugin, via the new `routeOptions` property on the plugin constructor. + ## 3.8.0 * Set explicit Content-Type headers in fastify plugin routes. diff --git a/conformance/src/fastify/app.ts b/conformance/src/fastify/app.ts index 8e64ada..e39a428 100644 --- a/conformance/src/fastify/app.ts +++ b/conformance/src/fastify/app.ts @@ -10,6 +10,14 @@ const app: FastifyPluginAsync = async (fastify): Promise }; } +export type ConformanceApiRoutes = 'getApiInfo' | 'getWidgets' | 'createWidget' | 'getWidget' | 'deleteWidget' | 'getWidgetBatch' | 'mirrorFields' | 'checkQuery' | 'checkPath' | 'mirrorHeaders' | 'mixed' | 'required' | 'mirrorBytes' | 'mirrorText' | 'bodyTypes'; + /** EXPERIMENTAL: The generated code for this plugin is subject to change/removal without a major version bump. */ export const conformanceApiPlugin: fastifyTypes.FastifyPluginAsync = async (fastify, opts) => { - const { serviceOrFactory, caseInsensitiveQueryStringKeys, includeErrorDetails } = opts; + const { serviceOrFactory, caseInsensitiveQueryStringKeys, includeErrorDetails, routeOptions } = opts; const getService = typeof serviceOrFactory === 'function' ? serviceOrFactory : () => serviceOrFactory; @@ -73,9 +78,12 @@ export const conformanceApiPlugin: fastifyTypes.FastifyPluginAsync { - const { serviceOrFactory, caseInsensitiveQueryStringKeys, includeErrorDetails } = opts; + const { serviceOrFactory, caseInsensitiveQueryStringKeys, includeErrorDetails, routeOptions } = opts; const getService = typeof serviceOrFactory === 'function' ? serviceOrFactory : () => serviceOrFactory; @@ -60,9 +60,12 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { }); } + const defaultBodyLimit = routeOptions?.['*']?.bodyLimit; + fastify.route({ url: '/', method: 'GET', + bodyLimit: routeOptions?.['getApiInfo']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { @@ -88,12 +91,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/widgets', method: 'GET', + bodyLimit: routeOptions?.['getWidgets']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { @@ -121,12 +125,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/widgets', method: 'POST', + bodyLimit: routeOptions?.['createWidget']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '201': { $ref: 'Widget' }, @@ -155,12 +160,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/widgets/:id', method: 'GET', + bodyLimit: routeOptions?.['getWidget']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { $ref: 'Widget' }, @@ -195,12 +201,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/widgets/:id', method: 'DELETE', + bodyLimit: routeOptions?.['deleteWidget']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '204': { type: 'object', additionalProperties: false }, @@ -234,12 +241,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/widgets/get', method: 'POST', + bodyLimit: routeOptions?.['getWidgetBatch']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { type: 'array', items: { type: 'object', properties: { value: { $ref: 'Widget' }, error: { $ref: '_error' } } } }, @@ -265,12 +273,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/mirrorFields', method: 'POST', + bodyLimit: routeOptions?.['mirrorFields']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { @@ -300,12 +309,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/checkQuery', method: 'GET', + bodyLimit: routeOptions?.['checkQuery']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { type: 'object', additionalProperties: false }, @@ -336,12 +346,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/checkPath/:string/:boolean/:float/:double/:int32/:int64/:decimal/:enum/:datetime', method: 'GET', + bodyLimit: routeOptions?.['checkPath']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { type: 'object', additionalProperties: false }, @@ -372,12 +383,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/mirrorHeaders', method: 'GET', + bodyLimit: routeOptions?.['mirrorHeaders']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { type: 'object', additionalProperties: false }, @@ -418,12 +430,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/mixed/:path', method: 'POST', + bodyLimit: routeOptions?.['mixed']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { @@ -470,12 +483,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/required', method: 'POST', + bodyLimit: routeOptions?.['required']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { @@ -514,12 +528,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/mirrorBytes', method: 'POST', + bodyLimit: routeOptions?.['mirrorBytes']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { type: 'string' }, @@ -550,12 +565,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/mirrorText', method: 'POST', + bodyLimit: routeOptions?.['mirrorText']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { type: 'string' }, @@ -586,12 +602,13 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); fastify.route({ url: '/bodyTypes', method: 'POST', + bodyLimit: routeOptions?.['bodyTypes']?.bodyLimit ?? defaultBodyLimit, schema: { response: { '200': { type: 'string' }, @@ -617,7 +634,7 @@ export const jsConformanceApiPlugin = async (fastify, opts) => { } else { throw new Error('Result must have exactly one set from: value, error'); } - } + }, }); } diff --git a/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs b/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs index 735cb28..2b9c32d 100644 --- a/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs +++ b/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs @@ -692,7 +692,14 @@ private CodeGenOutput GenerateFastifyPluginOutput(ServiceInfo service) code.WriteLine(); WriteJsDoc(code, "Whether to include error details in the response. Defaults to false."); code.WriteLine("includeErrorDetails?: boolean;"); + + code.WriteLine(); + WriteJsDoc(code, "Additional supported route options to use with routes. Only `bodyLimit` is currently supported. Use '*' to apply to all routes."); + code.WriteLine($"routeOptions?: {{ [K in '*' | {capModuleName}Routes]?: Pick }};"); } + + code.WriteLine(); + code.WriteLine($"export type {capModuleName}Routes = {string.Join(" | ", httpServiceInfo.Methods.Select(m => $"'{m.ServiceMethod.Name}'"))};"); } else { @@ -703,7 +710,7 @@ private CodeGenOutput GenerateFastifyPluginOutput(ServiceInfo service) WriteJsDoc(code, "EXPERIMENTAL: The generated code for this plugin is subject to change/removal without a major version bump."); using (code.Block($"export const {camelCaseModuleName}Plugin" + IfTypeScript($": fastifyTypes.FastifyPluginAsync<{capModuleName}PluginOptions>") + " = async (fastify, opts) => {", "}")) { - code.WriteLine("const { serviceOrFactory, caseInsensitiveQueryStringKeys, includeErrorDetails } = opts;"); + code.WriteLine("const { serviceOrFactory, caseInsensitiveQueryStringKeys, includeErrorDetails, routeOptions } = opts;"); code.WriteLine(); code.WriteLine("const getService = typeof serviceOrFactory === 'function' ? serviceOrFactory : () => serviceOrFactory;"); @@ -781,6 +788,9 @@ private CodeGenOutput GenerateFastifyPluginOutput(ServiceInfo service) } } + code.WriteLine(); + code.WriteLine("const defaultBodyLimit = routeOptions?.['*']?.bodyLimit;"); + foreach (var httpMethodInfo in httpServiceInfo.Methods) { var methodName = httpMethodInfo.ServiceMethod.Name; @@ -794,6 +804,7 @@ private CodeGenOutput GenerateFastifyPluginOutput(ServiceInfo service) { code.WriteLine($"url: '{fastifyPath}',"); code.WriteLine($"method: '{httpMethodInfo.Method.ToUpperInvariant()}',"); + code.WriteLine($"bodyLimit: routeOptions?.['{httpMethodInfo.ServiceMethod.Name}']?.bodyLimit ?? defaultBodyLimit,"); using (code.Block("schema: {", "},")) { using (code.Block("response: {", "},")) @@ -829,7 +840,7 @@ private CodeGenOutput GenerateFastifyPluginOutput(ServiceInfo service) code.WriteLine("'5xx': { $ref: '_error' },"); } } - using (code.Block("handler: async function (req, res) {", "}")) + using (code.Block("handler: async function (req, res) {", "},")) { code.WriteLine("const request" + IfTypeScript($": Partial") + " = {};"); if (httpMethodInfo.PathFields.Count != 0)