diff --git a/.github/workflows/samples-typescript-nestjs-server.yaml b/.github/workflows/samples-typescript-nestjs-server.yaml index e51d9e5e28e0..0b3d0b3d0331 100644 --- a/.github/workflows/samples-typescript-nestjs-server.yaml +++ b/.github/workflows/samples-typescript-nestjs-server.yaml @@ -5,6 +5,7 @@ on: paths: - samples/server/petstore/typescript-nestjs-server/** - .github/workflows/samples-typescript-nestjs-server.yaml + - .github/workflows/samples-typescript-nestjs-server-parameters.yaml jobs: build: name: Test TypeScript NestJS Server diff --git a/bin/configs/typescript-nestjs-server-parameters.yaml b/bin/configs/typescript-nestjs-server-parameters.yaml new file mode 100644 index 000000000000..f4ca4a50e7ee --- /dev/null +++ b/bin/configs/typescript-nestjs-server-parameters.yaml @@ -0,0 +1,6 @@ +generatorName: typescript-nestjs-server +outputDir: samples/server/petstore/typescript-nestjs-server/builds/parameters +inputSpec: modules/openapi-generator/src/test/resources/3_0/parameter-test-spec.yaml +templateDir: modules/openapi-generator/src/main/resources/typescript-nestjs-server +additionalProperties: + "useSingleRequestParameter" : true \ No newline at end of file diff --git a/docs/generators/typescript-nestjs-server.md b/docs/generators/typescript-nestjs-server.md index 3f15cd815bfe..47869311674d 100644 --- a/docs/generators/typescript-nestjs-server.md +++ b/docs/generators/typescript-nestjs-server.md @@ -40,14 +40,14 @@ These options may be applied as additional-properties (cli) or configOptions (pl |nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false| |paramNaming|Naming convention for parameters: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase| |prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false| -|rxjsVersion|The version of RxJS compatible with Angular (see ngVersion option).| |null| +|rxjsVersion|The version of RxJS.| |null| |snapshot|When setting this property to true, the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm| |false| |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |stringEnums|Generate string enums instead of objects for enum values.| |false| |supportsES6|Generate code that conforms to ES6.| |false| |taggedUnions|Use discriminators to create tagged unions instead of extending interfaces.| |false| -|tsVersion|The version of typescript compatible with Angular (see ngVersion option).| |null| +|tsVersion|The version of typescript.| |null| |useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |false| ## IMPORT MAPPING diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java index 46c8b20a74b4..8e8a9a2fdc1d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java @@ -120,8 +120,8 @@ public TypeScriptNestjsServerCodegen() { this.cliOptions.add(new CliOption(FILE_NAMING, "Naming convention for the output files: 'camelCase', 'kebab-case'.").defaultValue(this.fileNaming)); this.cliOptions.add(new CliOption(STRING_ENUMS, STRING_ENUMS_DESC).defaultValue(String.valueOf(this.stringEnums))); this.cliOptions.add(new CliOption(USE_SINGLE_REQUEST_PARAMETER, "Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.").defaultValue(Boolean.FALSE.toString())); - this.cliOptions.add(new CliOption(TS_VERSION, "The version of typescript compatible with Angular (see ngVersion option).")); - this.cliOptions.add(new CliOption(RXJS_VERSION, "The version of RxJS compatible with Angular (see ngVersion option).")); + this.cliOptions.add(new CliOption(TS_VERSION, "The version of typescript.")); + this.cliOptions.add(new CliOption(RXJS_VERSION, "The version of RxJS.")); } @Override @@ -156,6 +156,9 @@ public void processOpts() { supportingFiles.add(new SupportingFile("api-implementations.mustache", "", "api-implementations.ts")); supportingFiles.add(new SupportingFile("api.module.mustache", "", "api.module.ts")); supportingFiles.add(new SupportingFile("controllers.mustache", "controllers", "index.ts")); + supportingFiles.add(new SupportingFile("cookies-decorator.mustache", "decorators", "cookies-decorator.ts")); + supportingFiles.add(new SupportingFile("headers-decorator.mustache", "decorators", "headers-decorator.ts")); + supportingFiles.add(new SupportingFile("decorators.mustache", "decorators", "index.ts")); supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore")); supportingFiles.add(new SupportingFile("README.md", "", "README.md")); supportingFiles.add(new SupportingFile("tsconfig.mustache", "", "tsconfig.json")); @@ -173,7 +176,7 @@ public void processOpts() { additionalProperties.put(NEST_VERSION, nestVersion); if (additionalProperties.containsKey(NPM_NAME)) { - if(!additionalProperties.containsKey(NPM_VERSION)) { + if (!additionalProperties.containsKey(NPM_VERSION)) { additionalProperties.put(NPM_VERSION, "0.0.0"); } @@ -274,7 +277,21 @@ private String applyLocalTypeMapping(String type) { } private boolean isLanguagePrimitive(String type) { - return languageSpecificPrimitives.contains(type); + return languageSpecificPrimitives.contains(type) || isInlineUnion(type); + } + + /** + *

+ * Determines if the given type is an inline union of strings, described as an enum without being an explicit component in OpenAPI spec. + *

+ * Example input that matches: {@code "'A' | 'B'" } + * + * @param type The Typescript type to evaluate. + */ + private boolean isInlineUnion(String type) { + return Arrays.stream(type.split("\\|")) + .map(String::trim) + .allMatch(value -> value.matches("([\"'].*[\"'])")); } private boolean isLanguageGenericType(String type) { @@ -294,6 +311,9 @@ private boolean isRecordType(String type) { public void postProcessParameter(CodegenParameter parameter) { super.postProcessParameter(parameter); parameter.dataType = applyLocalTypeMapping(parameter.dataType); + if ("undefined".equals(parameter.defaultValue)) { + parameter.defaultValue = null; + } } @Override @@ -343,8 +363,8 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L // Collect imports from parameters if (operation.allParams != null) { for (CodegenParameter param : operation.allParams) { - if(param.dataType != null) { - if(isLanguageGenericType(param.dataType)) { + if (param.dataType != null) { + if (isLanguageGenericType(param.dataType)) { // Extract generic type and add to imports if its not a primitive String genericType = extractGenericType(param.dataType); if (genericType != null && !isLanguagePrimitive(genericType) && !isRecordType(genericType)) { @@ -366,10 +386,10 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L if (isLanguageGenericType(operation.returnType)) { // Extract generic type and add to imports if it's not a primitive String genericType = extractGenericType(operation.returnType); - if (genericType != null && !isLanguagePrimitive(genericType) && !isRecordType(genericType)) { + if (needToImport(operation.returnType) && genericType != null && !isLanguagePrimitive(genericType) && !isRecordType(genericType)) { allImports.add(genericType); } - } else { + } else if (needToImport(operation.returnType)) { allImports.add(operation.returnType); } } @@ -397,10 +417,10 @@ private String extractGenericType(String type) { return null; } String genericType = type.substring(startAngleBracketIndex + 1, endAngleBracketIndex); - if(isLanguageGenericType(genericType)) { + if (isLanguageGenericType(genericType)) { return extractGenericType(type); } - if(genericType.contains("|")) { + if (genericType.contains("|")) { return null; } return genericType; @@ -429,7 +449,11 @@ private Set parseImports(CodegenModel cm) { for (String name : cm.imports) { if (name.indexOf(" | ") >= 0) { String[] parts = name.split(" \\| "); - Collections.addAll(newImports, parts); + for (String part : parts) { + if (needToImport(part)) { + newImports.add(part); + } + } } else { newImports.add(name); } diff --git a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/api.mustache b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/api.mustache index f67fafe6f81a..ce63e0c6c4c3 100644 --- a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/api.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/api.mustache @@ -1,13 +1,15 @@ import { Injectable } from '@nestjs/common'; import { Observable } from 'rxjs'; +{{#tsImports.0}} import { {{#tsImports}}{{classname}}, {{/tsImports}} } from '../{{modelPackage}}'; +{{/tsImports.0}} {{#useSingleRequestParameter}} {{#operations}} {{#operation}} export type {{#lambda.pascalcase}}{{operationId}}{{/lambda.pascalcase}}RequestParams = { {{#allParams}} - {{paramName}}: {{{dataType}}} + {{paramName}}: {{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{^required}} | undefined{{/required}} {{/allParams}} } {{/operation}} @@ -23,7 +25,7 @@ export abstract class {{classname}} { {{/useSingleRequestParameter}} {{^useSingleRequestParameter}} - abstract {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}, {{/allParams}} request: Request): {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} | Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> | Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>; + abstract {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{^required}} | undefined{{/required}}, {{/allParams}} request: Request): {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} | Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> | Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>; {{/useSingleRequestParameter}} {{/operation}} diff --git a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/controller.mustache b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/controller.mustache index c761a37d5d8f..5438aa5b185c 100644 --- a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/controller.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/controller.mustache @@ -1,7 +1,10 @@ -import { Body, Controller{{#httpMethods}}, {{.}}{{/httpMethods}}, Param, Query, Req } from '@nestjs/common'; +import { Body, Controller, DefaultValuePipe{{#httpMethods}}, {{.}}{{/httpMethods}}, Param, ParseIntPipe, ParseFloatPipe, Query, Req } from '@nestjs/common'; import { Observable } from 'rxjs'; +import { Cookies, Headers } from '../decorators'; import { {{classname}} } from '../{{apiPackage}}'; +{{#tsImports.0}} import { {{#tsImports}}{{classname}}, {{/tsImports}} } from '../{{modelPackage}}'; +{{/tsImports.0}} @Controller() export class {{classname}}Controller { @@ -10,7 +13,7 @@ export class {{classname}}Controller { {{#operations}} {{#operation}} @{{#vendorExtensions.x-http-method}}{{.}}{{/vendorExtensions.x-http-method}}{{^vendorExtensions.x-http-method}}{{httpMethod}}{{/vendorExtensions.x-http-method}}('{{path}}') - {{operationId}}({{#allParams}}{{#isPathParam}}@Param('{{paramName}}') {{/isPathParam}}{{#isQueryParam}}@Query('{{paramName}}') {{/isQueryParam}}{{#isBodyParam}}@Body() {{/isBodyParam}}{{paramName}}: {{{dataType}}}, {{/allParams}}@Req() request: Request): {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} | Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> | Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { + {{operationId}}({{#allParams}}{{#isPathParam}}@Param('{{baseName}}'{{>paramPipe}}) {{/isPathParam}}{{#isQueryParam}}@Query('{{baseName}}'{{>paramPipe}}) {{/isQueryParam}}{{#isHeaderParam}}@Headers('{{baseName}}'{{>paramPipe}}) {{/isHeaderParam}}{{#isCookieParam}}@Cookies('{{baseName}}'{{>paramPipe}}) {{/isCookieParam}}{{#isBodyParam}}@Body() {{/isBodyParam}}{{paramName}}: {{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{^required}} | undefined{{/required}}, {{/allParams}}@Req() request: Request): {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} | Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> | Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { return this.{{classVarName}}.{{operationId}}({{#useSingleRequestParameter}}{ {{/useSingleRequestParameter}}{{#allParams}}{{paramName}}, {{/allParams}}{{#useSingleRequestParameter}}}, {{/useSingleRequestParameter}}request); } diff --git a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/cookies-decorator.mustache b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/cookies-decorator.mustache new file mode 100644 index 000000000000..e61332374128 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/cookies-decorator.mustache @@ -0,0 +1,24 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** +* A decorator function for retrieving cookies from the request object in an HTTP context. +* +* This decorator only works, if the framework specific cookie middleware is installed and enabled. +* - For Express, you need to use the `cookie-parser` middleware. +* - For Fastify, you need to use the `@fastify/cookie` plugin. +* +* Consult https://docs.nestjs.com/techniques/cookies for further information +* +* Usage: +* ``` +* @Get() +* findAll(@Cookies('name') name: string) {} +* ``` +*/ +export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + if (!data) { + return { ...request.cookies, ...request.signedCookies }; + } + return request.cookies?.[data] ?? request.signedCookies?.[data]; +}); \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/decorators.mustache b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/decorators.mustache new file mode 100644 index 000000000000..4bd1d7be5b42 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/decorators.mustache @@ -0,0 +1,2 @@ +export * from './cookies-decorator'; +export * from './headers-decorator'; \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/headers-decorator.mustache b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/headers-decorator.mustache new file mode 100644 index 000000000000..0648f28feba1 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/headers-decorator.mustache @@ -0,0 +1,16 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** +* A decorator function for retrieving headers from the request object in an HTTP context. +* Workaround for enabling PipeTransformers on Headers (see https://github.com/nestjs/nest/issues/356) +* +* Usage: +* ``` +* @Get() +* findAll(@Headers('name') name: string) {} +* ``` +*/ +export const Headers = createParamDecorator((data: string, ctx: ExecutionContext) => { +const request = ctx.switchToHttp().getRequest(); +return data ? request.headers?.[data.toLowerCase()] : request.headers; +}); \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/typescript-nestjs-server/paramPipe.mustache b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/paramPipe.mustache new file mode 100644 index 000000000000..9803a8fd8dce --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-nestjs-server/paramPipe.mustache @@ -0,0 +1 @@ +{{#defaultValue}}, new DefaultValuePipe({{{defaultValue}}}){{/defaultValue}}{{#isNumber}}, new {{#isFloat}}ParseFloatPipe({{/isFloat}}{{^isFloat}}ParseIntPipe({{/isFloat}}{{^isRequired}}{optional: true}{{/isRequired}}{{#isNullable}}{optional: true}{{/isNullable}}){{/isNumber}} \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/.openapi-generator/FILES b/samples/server/petstore/typescript-nestjs-server/builds/default/.openapi-generator/FILES index cae2c93f2c35..9196bafc8c4e 100644 --- a/samples/server/petstore/typescript-nestjs-server/builds/default/.openapi-generator/FILES +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/.openapi-generator/FILES @@ -10,6 +10,9 @@ controllers/PetApi.controller.ts controllers/StoreApi.controller.ts controllers/UserApi.controller.ts controllers/index.ts +decorators/cookies-decorator.ts +decorators/headers-decorator.ts +decorators/index.ts index.ts models/api-response.ts models/category.ts diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/api/PetApi.ts b/samples/server/petstore/typescript-nestjs-server/builds/default/api/PetApi.ts index b6a3a4f5da5c..5005435ad0de 100644 --- a/samples/server/petstore/typescript-nestjs-server/builds/default/api/PetApi.ts +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/api/PetApi.ts @@ -9,7 +9,7 @@ export abstract class PetApi { abstract addPet(pet: Pet, request: Request): Pet | Promise | Observable; - abstract deletePet(petId: number, apiKey: string, request: Request): void | Promise | Observable; + abstract deletePet(petId: number, apiKey: string | undefined, request: Request): void | Promise | Observable; abstract findPetsByStatus(status: Array<'available' | 'pending' | 'sold'>, request: Request): Array | Promise> | Observable>; @@ -24,9 +24,9 @@ export abstract class PetApi { abstract updatePet(pet: Pet, request: Request): Pet | Promise | Observable; - abstract updatePetWithForm(petId: number, name: string, status: string, request: Request): void | Promise | Observable; + abstract updatePetWithForm(petId: number, name: string | undefined, status: string | undefined, request: Request): void | Promise | Observable; - abstract uploadFile(petId: number, additionalMetadata: string, file: Blob, request: Request): ApiResponse | Promise | Observable; + abstract uploadFile(petId: number, additionalMetadata: string | undefined, file: Blob | undefined, request: Request): ApiResponse | Promise | Observable; } \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/PetApi.controller.ts b/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/PetApi.controller.ts index 8e5f0ffd771d..1dd604f5b73a 100644 --- a/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/PetApi.controller.ts +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/PetApi.controller.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Delete, Get, Post, Put, Param, Query, Req } from '@nestjs/common'; +import { Body, Controller, DefaultValuePipe, Delete, Get, Post, Put, Param, ParseIntPipe, ParseFloatPipe, Query, Req } from '@nestjs/common'; import { Observable } from 'rxjs'; +import { Cookies, Headers } from '../decorators'; import { PetApi } from '../api'; import { ApiResponse, Pet, } from '../models'; @@ -13,7 +14,7 @@ export class PetApiController { } @Delete('/pet/:petId') - deletePet(@Param('petId') petId: number, apiKey: string, @Req() request: Request): void | Promise | Observable { + deletePet(@Param('petId') petId: number, @Headers('api_key') apiKey: string | undefined, @Req() request: Request): void | Promise | Observable { return this.petApi.deletePet(petId, apiKey, request); } @@ -38,12 +39,12 @@ export class PetApiController { } @Post('/pet/:petId') - updatePetWithForm(@Param('petId') petId: number, name: string, status: string, @Req() request: Request): void | Promise | Observable { + updatePetWithForm(@Param('petId') petId: number, name: string | undefined, status: string | undefined, @Req() request: Request): void | Promise | Observable { return this.petApi.updatePetWithForm(petId, name, status, request); } @Post('/pet/:petId/uploadImage') - uploadFile(@Param('petId') petId: number, additionalMetadata: string, file: Blob, @Req() request: Request): ApiResponse | Promise | Observable { + uploadFile(@Param('petId') petId: number, additionalMetadata: string | undefined, file: Blob | undefined, @Req() request: Request): ApiResponse | Promise | Observable { return this.petApi.uploadFile(petId, additionalMetadata, file, request); } diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/StoreApi.controller.ts b/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/StoreApi.controller.ts index f32cd6358dfd..c9e5f52526df 100644 --- a/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/StoreApi.controller.ts +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/StoreApi.controller.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Delete, Get, Post, Param, Query, Req } from '@nestjs/common'; +import { Body, Controller, DefaultValuePipe, Delete, Get, Post, Param, ParseIntPipe, ParseFloatPipe, Query, Req } from '@nestjs/common'; import { Observable } from 'rxjs'; +import { Cookies, Headers } from '../decorators'; import { StoreApi } from '../api'; import { Order, } from '../models'; diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/UserApi.controller.ts b/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/UserApi.controller.ts index d8c9ed60f24c..797964b18e85 100644 --- a/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/UserApi.controller.ts +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/controllers/UserApi.controller.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Delete, Get, Post, Put, Param, Query, Req } from '@nestjs/common'; +import { Body, Controller, DefaultValuePipe, Delete, Get, Post, Put, Param, ParseIntPipe, ParseFloatPipe, Query, Req } from '@nestjs/common'; import { Observable } from 'rxjs'; +import { Cookies, Headers } from '../decorators'; import { UserApi } from '../api'; import { User, } from '../models'; diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/cookies-decorator.ts b/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/cookies-decorator.ts new file mode 100644 index 000000000000..e61332374128 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/cookies-decorator.ts @@ -0,0 +1,24 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** +* A decorator function for retrieving cookies from the request object in an HTTP context. +* +* This decorator only works, if the framework specific cookie middleware is installed and enabled. +* - For Express, you need to use the `cookie-parser` middleware. +* - For Fastify, you need to use the `@fastify/cookie` plugin. +* +* Consult https://docs.nestjs.com/techniques/cookies for further information +* +* Usage: +* ``` +* @Get() +* findAll(@Cookies('name') name: string) {} +* ``` +*/ +export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + if (!data) { + return { ...request.cookies, ...request.signedCookies }; + } + return request.cookies?.[data] ?? request.signedCookies?.[data]; +}); \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/headers-decorator.ts b/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/headers-decorator.ts new file mode 100644 index 000000000000..0648f28feba1 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/headers-decorator.ts @@ -0,0 +1,16 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** +* A decorator function for retrieving headers from the request object in an HTTP context. +* Workaround for enabling PipeTransformers on Headers (see https://github.com/nestjs/nest/issues/356) +* +* Usage: +* ``` +* @Get() +* findAll(@Headers('name') name: string) {} +* ``` +*/ +export const Headers = createParamDecorator((data: string, ctx: ExecutionContext) => { +const request = ctx.switchToHttp().getRequest(); +return data ? request.headers?.[data.toLowerCase()] : request.headers; +}); \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/index.ts b/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/index.ts new file mode 100644 index 000000000000..4bd1d7be5b42 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/default/decorators/index.ts @@ -0,0 +1,2 @@ +export * from './cookies-decorator'; +export * from './headers-decorator'; \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/.gitignore b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator-ignore b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator-ignore new file mode 100644 index 000000000000..7484ee590a38 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator/FILES b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator/FILES new file mode 100644 index 000000000000..b193787b4883 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator/FILES @@ -0,0 +1,14 @@ +.gitignore +README.md +api-implementations.ts +api.module.ts +api/DefaultApi.ts +api/index.ts +controllers/DefaultApi.controller.ts +controllers/index.ts +decorators/cookies-decorator.ts +decorators/headers-decorator.ts +decorators/index.ts +index.ts +models/index.ts +tsconfig.json diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator/VERSION b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator/VERSION new file mode 100644 index 000000000000..193a12d6e891 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.20.0-SNAPSHOT diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/README.md b/samples/server/petstore/typescript-nestjs-server/builds/parameters/README.md new file mode 100644 index 000000000000..6fe10cd8d8ee --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/README.md @@ -0,0 +1,56 @@ +# OpenApi Generator _typescript-nestjs-server_ + +Usage: The generated output is intended to be its own module, that can be imported into your NestJS App Module. You do not need to change generated files, just import the module and implement the API + +Example usage (with the openapi sample `petstore.yaml`): + +1. Invoke openapi-generator + ``` + openapi-generator-cli.jar generate -i petstore.yaml -g typescript-nestjs-server -o api-module/ + ``` +2. implement the contracts from `api-module/api` + `handlers/PetService.ts`: + ```typescript + import { Pet, ApiResponse } from "models"; + import { Observable } from "rxjs"; + import { PetApi } from "../api"; + import { Inject, Injectable } from "@nestjs/common"; + + @Injectable() + export class PetService implements PetApi { + addPet(pet: Pet, request: Request): Pet | Promise | Observable { + throw new Error("Method not implemented."); + } + + deletePet(petId: number, apiKey: string, request: Request): void | Promise | Observable { + throw new Error("Method not implemented."); + } + + ... + ``` +3. Import the generated `ApiModule` with `ApiModule.forRoot` and provide a instance of `ApiImplementations` with a reference to your implementation + `app.module.ts` + ```typescript + import { Module } from "@nestjs/common"; + import { ApiModule, ApiImplementations } from "api-module"; + import { PetService } from "./handlers/PetService"; + import { UserService } from "./handlers/UserService"; + import { StoreService } from "./handlers/StoreService"; + + const apiImplementations: ApiImplementations = { + petApi: PetService, + userApi: UserService, + storeApi: StoreService, + } + + @Module({ + imports: [ + ApiModule.forRoot(apiImplementations), + ], + controllers: [], + providers: [], + }) + export class AppModule {} + ``` + +You now can regenerate the API module as often as you want without overriding your implementation. \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/api-implementations.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api-implementations.ts new file mode 100644 index 000000000000..b3e65c743c6b --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api-implementations.ts @@ -0,0 +1,9 @@ +import { Type } from '@nestjs/common'; +import { DefaultApi } from './api'; + +/** + * Provide this type to {@link ApiModule} to provide your API implementations +**/ +export type ApiImplementations = { + defaultApi: Type +}; diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/api.module.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api.module.ts new file mode 100644 index 000000000000..e36a211b6772 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api.module.ts @@ -0,0 +1,37 @@ +import { DynamicModule, Module, Provider } from '@nestjs/common'; +import { ApiImplementations } from './api-implementations' +import { DefaultApi } from './api'; +import { DefaultApiController } from './controllers'; + +export type ApiModuleConfiguration = { + /** + * your Api implementations + */ + apiImplementations: ApiImplementations, + /** + * additional Providers that may be used by your implementations + */ + providers?: Provider[], +} + +@Module({}) +export class ApiModule { + static forRoot(configuration: ApiModuleConfiguration): DynamicModule { + const providers: Provider[] = [ + { + provide: DefaultApi, + useClass: configuration.apiImplementations.defaultApi + }, + ...(configuration.providers || []), + ]; + + return { + module: ApiModule, + controllers: [ + DefaultApiController, + ], + providers: [...providers], + exports: [...providers] + } + } +} \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/api/DefaultApi.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api/DefaultApi.ts new file mode 100644 index 000000000000..5e59e6ad4d11 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api/DefaultApi.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { Observable } from 'rxjs'; + +export type FindPetsByStatusRequestParams = { + pathDefault: string + pathNullable: string + queryDefault: string | undefined + queryDefaultEnum: 'A' | 'B' | 'C' | undefined + queryDefaultInt: number | undefined + headerDefault: string | undefined + headerDefaultEnum: 'A' | 'B' | 'C' | undefined + headerDefaultInt: number | undefined + cookieDefault: string | undefined + cookieDefaultEnum: 'A' | 'B' | 'C' | undefined + cookieDefaultInt: number | undefined + queryNullable: string | null | undefined + headerNullable: string | null | undefined + cookieNullable: string | null | undefined + $query$dollarSign: string | undefined +} + +@Injectable() +export abstract class DefaultApi { + abstract findPetsByStatus(findPetsByStatusRequestParams: FindPetsByStatusRequestParams, request: Request): void | Promise | Observable; + + +} \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/api/index.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api/index.ts new file mode 100644 index 000000000000..a1aa4698ff25 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/api/index.ts @@ -0,0 +1 @@ +export * from './DefaultApi'; diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/controllers/DefaultApi.controller.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/controllers/DefaultApi.controller.ts new file mode 100644 index 000000000000..da8e5fc3fba6 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/controllers/DefaultApi.controller.ts @@ -0,0 +1,15 @@ +import { Body, Controller, DefaultValuePipe, Get, Param, ParseIntPipe, ParseFloatPipe, Query, Req } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { Cookies, Headers } from '../decorators'; +import { DefaultApi } from '../api'; + +@Controller() +export class DefaultApiController { + constructor(private readonly defaultApi: DefaultApi) {} + + @Get('/test/parameters/:path_default/:path_nullable') + findPetsByStatus(@Param('path_default') pathDefault: string, @Param('path_nullable') pathNullable: string, @Query('query_default', new DefaultValuePipe('available')) queryDefault: string | undefined, @Query('query_default_enum', new DefaultValuePipe('B')) queryDefaultEnum: 'A' | 'B' | 'C' | undefined, @Query('query_default_int', new DefaultValuePipe(3), new ParseIntPipe({optional: true})) queryDefaultInt: number | undefined, @Headers('header_default', new DefaultValuePipe('available')) headerDefault: string | undefined, @Headers('header_default_enum', new DefaultValuePipe('B')) headerDefaultEnum: 'A' | 'B' | 'C' | undefined, @Headers('header_default_int', new DefaultValuePipe(3), new ParseIntPipe({optional: true})) headerDefaultInt: number | undefined, @Cookies('cookie_default', new DefaultValuePipe('available')) cookieDefault: string | undefined, @Cookies('cookie_default_enum', new DefaultValuePipe('B')) cookieDefaultEnum: 'A' | 'B' | 'C' | undefined, @Cookies('cookie_default_int', new DefaultValuePipe(3), new ParseIntPipe({optional: true})) cookieDefaultInt: number | undefined, @Query('query_nullable') queryNullable: string | null | undefined, @Headers('header_nullable') headerNullable: string | null | undefined, @Cookies('cookie_nullable') cookieNullable: string | null | undefined, @Query('$query-$dollar-sign') $query$dollarSign: string | undefined, @Req() request: Request): void | Promise | Observable { + return this.defaultApi.findPetsByStatus({ pathDefault, pathNullable, queryDefault, queryDefaultEnum, queryDefaultInt, headerDefault, headerDefaultEnum, headerDefaultInt, cookieDefault, cookieDefaultEnum, cookieDefaultInt, queryNullable, headerNullable, cookieNullable, $query$dollarSign, }, request); + } + +} \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/controllers/index.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/controllers/index.ts new file mode 100644 index 000000000000..d3d9a7becb6b --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/controllers/index.ts @@ -0,0 +1 @@ +export * from './DefaultApi.controller'; diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/cookie-decorator.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/cookie-decorator.ts new file mode 100644 index 000000000000..e9fdcf7354f0 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/cookie-decorator.ts @@ -0,0 +1,6 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { +const request = ctx.switchToHttp().getRequest(); +return data ? request.cookies?.[data] : request.cookies; +}); \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/cookies-decorator.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/cookies-decorator.ts new file mode 100644 index 000000000000..19927e8c299c --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/cookies-decorator.ts @@ -0,0 +1,21 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** +* A decorator function for retrieving cookies from the request object in an HTTP context. +* +* This decorator only works, if the framework specific cookie middleware is installed and enabled. +* - For Express, you need to use the `cookie-parser` middleware. +* - For Fastify, you need to use the `@fastify/cookie` plugin. +* +* Consult https://docs.nestjs.com/techniques/cookies for further information +* +* Usage: +* ``` +* @Get() +* findAll(@Cookies('name') name: string) {} +* ``` +*/ +export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + return data ? request.cookies?.[data] : request.cookies; +}); \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/cookies-decorator.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/cookies-decorator.ts new file mode 100644 index 000000000000..e61332374128 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/cookies-decorator.ts @@ -0,0 +1,24 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** +* A decorator function for retrieving cookies from the request object in an HTTP context. +* +* This decorator only works, if the framework specific cookie middleware is installed and enabled. +* - For Express, you need to use the `cookie-parser` middleware. +* - For Fastify, you need to use the `@fastify/cookie` plugin. +* +* Consult https://docs.nestjs.com/techniques/cookies for further information +* +* Usage: +* ``` +* @Get() +* findAll(@Cookies('name') name: string) {} +* ``` +*/ +export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + if (!data) { + return { ...request.cookies, ...request.signedCookies }; + } + return request.cookies?.[data] ?? request.signedCookies?.[data]; +}); \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/headers-decorator.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/headers-decorator.ts new file mode 100644 index 000000000000..0648f28feba1 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/headers-decorator.ts @@ -0,0 +1,16 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** +* A decorator function for retrieving headers from the request object in an HTTP context. +* Workaround for enabling PipeTransformers on Headers (see https://github.com/nestjs/nest/issues/356) +* +* Usage: +* ``` +* @Get() +* findAll(@Headers('name') name: string) {} +* ``` +*/ +export const Headers = createParamDecorator((data: string, ctx: ExecutionContext) => { +const request = ctx.switchToHttp().getRequest(); +return data ? request.headers?.[data.toLowerCase()] : request.headers; +}); \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/index.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/index.ts new file mode 100644 index 000000000000..4bd1d7be5b42 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/decorators/index.ts @@ -0,0 +1,2 @@ +export * from './cookies-decorator'; +export * from './headers-decorator'; \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/index.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/index.ts new file mode 100644 index 000000000000..0f2a6865ff56 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/index.ts @@ -0,0 +1,2 @@ +export * from './api.module' +export * from './api-implementations' \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/models/index.ts b/samples/server/petstore/typescript-nestjs-server/builds/parameters/models/index.ts new file mode 100644 index 000000000000..0519ecba6ea9 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/models/index.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/builds/parameters/tsconfig.json b/samples/server/petstore/typescript-nestjs-server/builds/parameters/tsconfig.json new file mode 100644 index 000000000000..7f6bd7f1eb77 --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/builds/parameters/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2023", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/samples/server/petstore/typescript-nestjs-server/package-lock.json b/samples/server/petstore/typescript-nestjs-server/package-lock.json index fa553d0ffb9c..8099ebd15d17 100644 --- a/samples/server/petstore/typescript-nestjs-server/package-lock.json +++ b/samples/server/petstore/typescript-nestjs-server/package-lock.json @@ -12,6 +12,7 @@ "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.1.5", + "cookie-parser": "^1.4.7", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, @@ -23,6 +24,7 @@ "@nestjs/testing": "^11.0.1", "@swc/cli": "^0.6.0", "@swc/core": "^1.10.7", + "@types/cookie-parser": "^1.4.10", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", "@types/node": "^22.10.7", @@ -3147,6 +3149,16 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cookiejar": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", @@ -3369,6 +3381,14 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -5229,6 +5249,27 @@ "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/class-validator": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", + "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.20" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5504,6 +5545,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -8328,6 +8388,14 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.36", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.36.tgz", + "integrity": "sha512-woWhKMAVx1fzzUnMCyOzglgSgf6/AFHLASdOBcchYCyvWSGWt12imw3iu2hdI5d4dGZRsNWAmWiz37sDKUPaRQ==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -11158,6 +11226,17 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/samples/server/petstore/typescript-nestjs-server/package.json b/samples/server/petstore/typescript-nestjs-server/package.json index 5903b9f7aa05..f924c1123c13 100644 --- a/samples/server/petstore/typescript-nestjs-server/package.json +++ b/samples/server/petstore/typescript-nestjs-server/package.json @@ -19,6 +19,7 @@ "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.1.5", + "cookie-parser": "^1.4.7", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, @@ -30,6 +31,7 @@ "@nestjs/testing": "^11.0.1", "@swc/cli": "^0.6.0", "@swc/core": "^1.10.7", + "@types/cookie-parser": "^1.4.10", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", "@types/node": "^22.10.7", diff --git a/samples/server/petstore/typescript-nestjs-server/src/app.module.ts b/samples/server/petstore/typescript-nestjs-server/src/app.module.ts index fc5667a2e4ca..cf7f3e96d16d 100644 --- a/samples/server/petstore/typescript-nestjs-server/src/app.module.ts +++ b/samples/server/petstore/typescript-nestjs-server/src/app.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; -import { PetService } from './handlers/PetService'; -import { UserService } from './handlers/UserService'; -import { StoreService } from './handlers/StoreService'; +import { PetService } from './handlers/default/PetService'; +import { UserService } from './handlers/default/UserService'; +import { StoreService } from './handlers/default/StoreService'; import { ApiModule } from '../builds/default'; +import { ApiModule as ParamsModule } from '../builds/parameters'; import { TestService } from './TestService'; +import { DefaultService } from './handlers/parameters/DefaultService'; @Module({ imports: [ @@ -12,9 +14,14 @@ import { TestService } from './TestService'; petApi: PetService, userApi: UserService, storeApi: StoreService, - }, - providers: [TestService] -}), + }, + providers: [TestService], + }), + ParamsModule.forRoot({ + apiImplementations: { + defaultApi: DefaultService, + }, + }), ], controllers: [], providers: [], diff --git a/samples/server/petstore/typescript-nestjs-server/src/handlers/PetService.ts b/samples/server/petstore/typescript-nestjs-server/src/handlers/default/PetService.ts similarity index 91% rename from samples/server/petstore/typescript-nestjs-server/src/handlers/PetService.ts rename to samples/server/petstore/typescript-nestjs-server/src/handlers/default/PetService.ts index 858a93019a30..99b291766c48 100644 --- a/samples/server/petstore/typescript-nestjs-server/src/handlers/PetService.ts +++ b/samples/server/petstore/typescript-nestjs-server/src/handlers/default/PetService.ts @@ -1,9 +1,9 @@ import { Observable } from 'rxjs'; import { Injectable } from '@nestjs/common'; -import { PetApi } from '../../builds/default/api'; -import { ApiResponse, Pet } from '../../builds/default/models'; -import { TestService } from '../TestService' +import { PetApi } from '../../../builds/default/api'; +import { ApiResponse, Pet } from '../../../builds/default/models'; +import { TestService } from '../../TestService' @Injectable() export class PetService implements PetApi { diff --git a/samples/server/petstore/typescript-nestjs-server/src/handlers/StoreService.ts b/samples/server/petstore/typescript-nestjs-server/src/handlers/default/StoreService.ts similarity index 88% rename from samples/server/petstore/typescript-nestjs-server/src/handlers/StoreService.ts rename to samples/server/petstore/typescript-nestjs-server/src/handlers/default/StoreService.ts index dc299f65cea5..49d8f1a78e93 100644 --- a/samples/server/petstore/typescript-nestjs-server/src/handlers/StoreService.ts +++ b/samples/server/petstore/typescript-nestjs-server/src/handlers/default/StoreService.ts @@ -1,7 +1,7 @@ import { Observable } from 'rxjs'; import { Injectable } from '@nestjs/common'; -import { StoreApi } from '../../builds/default/api'; -import { Order } from '../../builds/default/models'; +import { StoreApi } from '../../../builds/default/api'; +import { Order } from '../../../builds/default/models'; @Injectable() export class StoreService implements StoreApi { diff --git a/samples/server/petstore/typescript-nestjs-server/src/handlers/UserService.ts b/samples/server/petstore/typescript-nestjs-server/src/handlers/default/UserService.ts similarity index 92% rename from samples/server/petstore/typescript-nestjs-server/src/handlers/UserService.ts rename to samples/server/petstore/typescript-nestjs-server/src/handlers/default/UserService.ts index 3e4e1257634b..7407c48ba611 100644 --- a/samples/server/petstore/typescript-nestjs-server/src/handlers/UserService.ts +++ b/samples/server/petstore/typescript-nestjs-server/src/handlers/default/UserService.ts @@ -1,7 +1,7 @@ import { Observable } from 'rxjs'; import { Injectable } from '@nestjs/common'; -import { UserApi } from '../../builds/default/api'; -import { User } from '../../builds/default/models'; +import { UserApi } from '../../../builds/default/api'; +import { User } from '../../../builds/default/models'; @Injectable() export class UserService implements UserApi { diff --git a/samples/server/petstore/typescript-nestjs-server/src/handlers/parameters/DefaultService.ts b/samples/server/petstore/typescript-nestjs-server/src/handlers/parameters/DefaultService.ts new file mode 100644 index 000000000000..acf0abfd872e --- /dev/null +++ b/samples/server/petstore/typescript-nestjs-server/src/handlers/parameters/DefaultService.ts @@ -0,0 +1,19 @@ +import { + DefaultApi, + FindPetsByStatusRequestParams, +} from '../../../builds/parameters/api'; +import { Observable } from 'rxjs'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class DefaultService extends DefaultApi { + + lastRequestParams?: FindPetsByStatusRequestParams; + + findPetsByStatus( + findPetsByStatusRequestParams: FindPetsByStatusRequestParams, + request: Request, + ): void | Promise | Observable { + this.lastRequestParams = findPetsByStatusRequestParams; + } +} \ No newline at end of file diff --git a/samples/server/petstore/typescript-nestjs-server/src/main.ts b/samples/server/petstore/typescript-nestjs-server/src/main.ts index f76bc8d977be..a7d3d3d52304 100644 --- a/samples/server/petstore/typescript-nestjs-server/src/main.ts +++ b/samples/server/petstore/typescript-nestjs-server/src/main.ts @@ -1,8 +1,11 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.use(cookieParser()); await app.listen(process.env.PORT ?? 3000); } + bootstrap(); diff --git a/samples/server/petstore/typescript-nestjs-server/test/app.e2e-spec.ts b/samples/server/petstore/typescript-nestjs-server/test/app.e2e-spec.ts index 84b901ad0568..45aa15d283e1 100644 --- a/samples/server/petstore/typescript-nestjs-server/test/app.e2e-spec.ts +++ b/samples/server/petstore/typescript-nestjs-server/test/app.e2e-spec.ts @@ -4,6 +4,9 @@ import * as request from 'supertest'; import { App } from 'supertest/types'; import { AppModule } from '../src/app.module'; import { PetApiController } from '../builds/default/controllers'; +import { DefaultService } from '../src/handlers/parameters/DefaultService'; +import { DefaultApi } from '../builds/parameters/api'; +import * as cookieParser from 'cookie-parser'; describe('AppModule (e2e)', () => { let app: INestApplication; @@ -14,11 +17,10 @@ describe('AppModule (e2e)', () => { }).compile(); app = moduleFixture.createNestApplication(); + app.use(cookieParser()); await app.init(); }); - - describe('PetApiController', () => { it('should be provided', () => { const petApiController = app.get(PetApiController); @@ -29,25 +31,101 @@ describe('AppModule (e2e)', () => { return request(app.getHttpServer()) .get('/pet/1') .expect(200) - .expect({name: 'MyPetB', photoUrls: []}); + .expect({ name: 'MyPetB', photoUrls: [] }); }); it('should handle POST request with body', () => { return request(app.getHttpServer()) .post('/pet') - .send({name: 'POST Pet'}) + .send({ name: 'POST Pet' }) .expect(201) - .expect({name: 'POST Pet'}); + .expect({ name: 'POST Pet' }); }); it('should handle GET request with query params', () => { return request(app.getHttpServer()) .get('/pet/findByStatus') - .query({status: ['available', 'pending']}) + .query({ status: ['available', 'pending'] }) .expect(200) - .expect([{name: 'available', photoUrls: []}, {name: 'pending', photoUrls: []}]); + .expect([ + { name: 'available', photoUrls: [] }, + { name: 'pending', photoUrls: [] }, + ]); }); - }) + }); + + describe('ParametersDefaultController', () => { + it('should set default parameters', () => { + let defaultService: DefaultService = app.get(DefaultApi); + return request(app.getHttpServer()) + .get('/test/parameters/path_a/path_b') + .expect((res)=> { + console.log('Body:', JSON.stringify(res.body, null, 2)); + }) + .expect(200) + .then(() => { + expect(defaultService.lastRequestParams).toEqual({ + pathDefault: 'path_a', + pathNullable: 'path_b', + queryDefault: 'available', + queryDefaultEnum: 'B', + queryDefaultInt: 3, + headerDefault: 'available', + headerDefaultEnum: 'B', + headerDefaultInt: 3, + cookieDefault: 'available', + cookieDefaultEnum: 'B', + cookieDefaultInt: 3, + queryNullable: undefined, + headerNullable: undefined, + cookieNullable: undefined, + $query$dollarSign: undefined, + }); + }); + }); + + it('should receive request parameters', () => { + let defaultService: DefaultService = app.get(DefaultApi); + + return request(app.getHttpServer()) + .get('/test/parameters/path_a/path_b') + .query({ + query_default: 'custom_query', + query_default_enum: 'C', + query_default_int: 5, + query_nullable: 'not_null_query', + '$query-$dollar-sign': '$$$', + }) + .set('header_default', 'custom_header') + .set('header_default_enum', 'C') + .set('header_default_int', '6') + .set('header_nullable', 'null') + .set( + 'Cookie', + 'cookie_default=custom_cookie; cookie_default_enum=C; cookie_default_int=7; cookie_nullable=a_cookie', + ) + .expect(200) + .then(() => { + expect(defaultService.lastRequestParams).toEqual({ + pathDefault: 'path_a', + pathNullable: 'path_b', + queryDefault: 'custom_query', + queryDefaultEnum: 'C', + queryDefaultInt: 5, + headerDefault: 'custom_header', + headerDefaultEnum: 'C', + headerDefaultInt: 6, + cookieDefault: 'custom_cookie', + cookieDefaultEnum: 'C', + cookieDefaultInt: 7, + queryNullable: 'not_null_query', + headerNullable: 'null', + cookieNullable: 'a_cookie', + $query$dollarSign: '$$$', + }); + }); + }); + }); });