From 603ce19476e88ecbdac193412fa92aec83abc54e Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Thu, 1 Oct 2026 11:43:35 -0700 Subject: [PATCH] fix(typescript): serialize JSON header parameter content --- .../codegen/CodegenParameter.java | 25 +++-- .../openapitools/codegen/DefaultCodegen.java | 2 + .../TypeScriptFetchClientCodegen.java | 98 ++++++++++++++++++- .../resources/typescript-fetch/apis.mustache | 15 +++ .../typescript-fetch/runtime.mustache | 4 +- .../typescript-node/api-single.mustache | 7 ++ .../TypeScriptFetchClientCodegenTest.java | 94 ++++++++++++++++++ .../TypeScriptNodeClientCodegenTest.java | 26 +++++ .../3_1/typescript-json-header-webhook.yaml | 26 +++++ .../resources/3_1/typescript-json-header.yaml | 68 +++++++++++++ 10 files changed, 353 insertions(+), 12 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_1/typescript-json-header-webhook.yaml create mode 100644 modules/openapi-generator/src/test/resources/3_1/typescript-json-header.yaml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java index 90c4c66558c0..81c056c6dbaa 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java @@ -50,6 +50,10 @@ public class CodegenParameter implements IJsonSchemaValidationProperties { * If a query parameter should be serialized as json */ public boolean queryIsJsonMimeType; + /** + * True when a header parameter uses a JSON media type in Parameter.content. + */ + public boolean headerIsJsonMimeType; /** * datatype is the generic inner parameter of a std::optional for C++, or Optional (Java) */ @@ -270,6 +274,7 @@ public CodegenParameter copy() { output.isArray = this.isArray; output.isMap = this.isMap; output.queryIsJsonMimeType = this.queryIsJsonMimeType; + output.headerIsJsonMimeType = this.headerIsJsonMimeType; output.isOptional = this.isOptional; output.isExplode = this.isExplode; output.style = this.style; @@ -294,15 +299,15 @@ public int hashCode() { isFormStyle, isSpaceDelimited, isPipeDelimited, jsonSchema, isString, isNumeric, isInteger, isLong, isNumber, isFloat, isDouble, isDecimal, isByteArray, isBinary, isBoolean, isDate, isDateTime, isUuid, isUri, isEmail, isPassword, - isFreeFormObject, isAnyType, isArray, isMap, queryIsJsonMimeType, isOptional, isFile, isEnum, isEnumRef, _enum, allowableValues, - items, mostInnerItems, additionalProperties, vars, requiredVars, vendorExtensions, hasValidation, - getMaxProperties(), getMinProperties(), isNullable, isDeprecated, required, getMaximum(), - getExclusiveMaximum(), getMinimum(), getExclusiveMinimum(), getMaxLength(), getMinLength(), - getPattern(), getMaxItems(), getMinItems(), getUniqueItems(), contentType, multipleOf, isNull, isVoid, - additionalPropertiesIsAnyType, hasVars, hasRequired, isShort, isUnboundedInteger, - hasDiscriminatorWithNonEmptyMapping, composedSchemas, hasMultipleTypes, schema, content, - requiredVarsMap, ref, uniqueItemsBoolean, schemaIsFromAdditionalProperties, - nameInPascalCase, nameInCamelCase, nameInLowerCase, nameInSnakeCase); + isFreeFormObject, isAnyType, isArray, isMap, queryIsJsonMimeType, headerIsJsonMimeType, isOptional, + isFile, isEnum, isEnumRef, _enum, allowableValues, items, mostInnerItems, additionalProperties, vars, + requiredVars, vendorExtensions, hasValidation, getMaxProperties(), getMinProperties(), isNullable, + isDeprecated, required, getMaximum(), getExclusiveMaximum(), getMinimum(), getExclusiveMinimum(), + getMaxLength(), getMinLength(), getPattern(), getMaxItems(), getMinItems(), getUniqueItems(), + contentType, multipleOf, isNull, isVoid, additionalPropertiesIsAnyType, hasVars, hasRequired, isShort, + isUnboundedInteger, hasDiscriminatorWithNonEmptyMapping, composedSchemas, hasMultipleTypes, schema, + content, requiredVarsMap, ref, uniqueItemsBoolean, schemaIsFromAdditionalProperties, nameInPascalCase, + nameInCamelCase, nameInLowerCase, nameInSnakeCase); } @Override @@ -345,6 +350,7 @@ public boolean equals(Object o) { isArray == that.isArray && isMap == that.isMap && queryIsJsonMimeType == that.queryIsJsonMimeType && + headerIsJsonMimeType == that.headerIsJsonMimeType && isOptional == that.isOptional && isFile == that.isFile && isEnum == that.isEnum && @@ -486,6 +492,7 @@ public String toString() { sb.append(", isArray=").append(isArray); sb.append(", isMap=").append(isMap); sb.append(", queryIsJsonMimeType=").append(queryIsJsonMimeType); + sb.append(", headerIsJsonMimeType=").append(headerIsJsonMimeType); sb.append(", isOptional=").append(isOptional); sb.append(", isFile=").append(isFile); sb.append(", isEnum=").append(isEnum); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index fc74115f6b4c..aea79c36dba9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -5722,6 +5722,8 @@ public CodegenParameter fromParameter(Parameter parameter, Set imports) codegenParameter.isPathParam = true; } else if (parameter instanceof HeaderParameter || "header".equalsIgnoreCase(parameter.getIn())) { codegenParameter.isHeaderParam = true; + codegenParameter.headerIsJsonMimeType = isJsonMimeType(codegenParameter.contentType) + || isJsonVendorMimeType(codegenParameter.contentType); } else if (parameter instanceof CookieParameter || "cookie".equalsIgnoreCase(parameter.getIn())) { codegenParameter.isCookieParam = true; } else { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java index 411521087608..f4b4726e6c9d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -19,8 +19,11 @@ import com.google.common.collect.ImmutableMap; import com.samskivert.mustache.Mustache; +import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.HeaderParameter; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.parameters.RequestBody; import io.swagger.v3.oas.models.responses.ApiResponse; @@ -34,6 +37,7 @@ import org.openapitools.codegen.model.ModelMap; import org.openapitools.codegen.model.ModelsMap; import org.openapitools.codegen.model.OperationsMap; +import org.openapitools.codegen.model.WebhooksMap; import org.openapitools.codegen.templating.mustache.IndentedLambda; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; @@ -355,6 +359,7 @@ public void processOpts() { if (!withoutRuntimeChecks) { this.modelTemplateFiles.put("models.mustache", ".ts"); } + additionalProperties.put("modelSerializersEnabled", !withoutRuntimeChecks); // `date` needs the model (de)serialization to convert with, which // withoutRuntimeChecks removes: the raw string would just be cast to Date. @@ -708,7 +713,81 @@ public List fromRequestBodyToFormParameters(RequestBody body, @Override public ExtendedCodegenParameter fromParameter(Parameter parameter, Set imports) { CodegenParameter cp = super.fromParameter(parameter, imports); - return new ExtendedCodegenParameter(cp); + ExtendedCodegenParameter result = new ExtendedCodegenParameter(cp); + Parameter resolvedParameter = ModelUtils.getReferencedParameter(openAPI, parameter); + result.jsonHeaderUsesModelSerializer = cp.headerIsJsonMimeType && cp.isModel + && hasGeneratedModelHeaderSchema(openAPI, resolvedParameter); + return result; + } + + @Override + public void processOpenAPI(OpenAPI openAPI) { + super.processOpenAPI(openAPI); + if (!withoutRuntimeChecks) { + return; + } + if (openAPI.getPaths() != null) { + for (PathItem path : openAPI.getPaths().values()) { + if (hasJsonContentModelHeader(openAPI, path)) { + enableJsonHeaderModelSerializers(); + return; + } + } + } + if (openAPI.getWebhooks() != null) { + for (PathItem webhook : openAPI.getWebhooks().values()) { + if (hasJsonContentModelHeader(openAPI, webhook)) { + enableJsonHeaderModelSerializers(); + return; + } + } + } + } + + private boolean hasJsonContentModelHeader(OpenAPI openAPI, PathItem path) { + if (hasJsonContentModelHeader(openAPI, path.getParameters())) { + return true; + } + for (Operation operation : path.readOperations()) { + if (hasJsonContentModelHeader(openAPI, operation.getParameters())) { + return true; + } + } + return false; + } + + private boolean hasJsonContentModelHeader(OpenAPI openAPI, List parameters) { + return parameters != null && parameters.stream() + .map(parameter -> ModelUtils.getReferencedParameter(openAPI, parameter)) + .anyMatch(parameter -> isJsonContentHeader(parameter) + && hasGeneratedModelHeaderSchema(openAPI, parameter)); + } + + private boolean isJsonContentHeader(Parameter parameter) { + if (parameter == null + || !(parameter instanceof HeaderParameter || "header".equalsIgnoreCase(parameter.getIn())) + || parameter.getSchema() != null + || parameter.getContent() == null + || parameter.getContent().isEmpty()) { + return false; + } + String contentType = parameter.getContent().keySet().iterator().next(); + return isJsonMimeType(contentType) || isJsonVendorMimeType(contentType); + } + + private boolean hasGeneratedModelHeaderSchema(OpenAPI openAPI, Parameter parameter) { + if (parameter.getContent() == null || parameter.getContent().isEmpty()) { + return false; + } + Schema schema = parameter.getContent().values().iterator().next().getSchema(); + return schema != null && schema.get$ref() != null + && ModelUtils.isModel(ModelUtils.getReferencedSchema(openAPI, schema)); + } + + private void enableJsonHeaderModelSerializers() { + modelTemplateFiles.put("models.mustache", ".ts"); + additionalProperties.put("jsonHeaderModelSerializers", true); + additionalProperties.put("modelSerializersEnabled", true); } @Override @@ -875,6 +954,20 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L return operations; } + @Override + public WebhooksMap postProcessWebhooksWithModels(WebhooksMap webhooks, List allModels) { + if (webhooks.getWebhooks().getOperation().stream() + .anyMatch(operation -> operation.headerParams != null && operation.headerParams.stream() + .anyMatch(parameter -> ((ExtendedCodegenParameter) parameter).jsonHeaderUsesModelSerializer))) { + for (Map im : webhooks.getImports()) { + String className = im.get("import").replace(modelPackage() + ".", ""); + im.put("className", className); + im.put("classFileName", convertUsingFileNamingConvention(className)); + } + } + return webhooks; + } + @Override public Map postProcessSupportingFileData(Map objs) { Map parentObjs = super.postProcessSupportingFileData(objs); @@ -1539,6 +1632,7 @@ private static String getItemsDataType(CodegenProperty items) { public class ExtendedCodegenParameter extends CodegenParameter { public String dataTypeAlternate; + public boolean jsonHeaderUsesModelSerializer; public boolean isUniqueId; // this parameter represents a unique id (x-isUniqueId: true) public List readOnlyVars; // a list of read-only properties public boolean hasReadOnly = false; // indicates the type has at least one read-only property @@ -1567,6 +1661,7 @@ public ExtendedCodegenParameter(CodegenParameter cp) { this.isQueryParam = cp.isQueryParam; this.isPathParam = cp.isPathParam; this.isHeaderParam = cp.isHeaderParam; + this.headerIsJsonMimeType = cp.headerIsJsonMimeType; this.isCookieParam = cp.isCookieParam; this.isBodyParam = cp.isBodyParam; this.isContainer = cp.isContainer; @@ -1655,6 +1750,7 @@ public ExtendedCodegenParameter copy() { CodegenParameter superCopy = super.copy(); ExtendedCodegenParameter output = new ExtendedCodegenParameter(superCopy); output.dataTypeAlternate = this.dataTypeAlternate; + output.jsonHeaderUsesModelSerializer = this.jsonHeaderUsesModelSerializer; output.isUniqueId = this.isUniqueId; return output; } diff --git a/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache b/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache index 8d939f5e29db..268f01408986 100644 --- a/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache @@ -9,6 +9,11 @@ import type { {{className}}, {{/imports}} } from '../models/index{{importFileExtension}}'; +{{#jsonHeaderModelSerializers}} +{{#imports}} +import { {{className}}ToJSON } from '../models/{{classFileName}}{{importFileExtension}}'; +{{/imports}} +{{/jsonHeaderModelSerializers}} {{/withoutRuntimeChecks}} {{^withoutRuntimeChecks}} {{#imports}} @@ -250,6 +255,15 @@ export class {{classname}} extends runtime.BaseAPI { {{/bodyParam}} {{/hasContentTypeRequestVariants}} {{#headerParams}} + {{#headerIsJsonMimeType}} + {{#jsonHeaderUsesModelSerializer}} + if (requestParameters['{{paramName}}'] !== undefined) { + headerParameters['{{baseName}}'] = JSON.stringify({{dataType}}ToJSON(requestParameters['{{paramName}}'])); + } + + {{/jsonHeaderUsesModelSerializer}} + {{/headerIsJsonMimeType}} + {{^jsonHeaderUsesModelSerializer}} {{#isArray}} if (requestParameters['{{paramName}}'] != null) { headerParameters['{{baseName}}'] = {{#uniqueItems}}Array.from({{/uniqueItems}}requestParameters['{{paramName}}']{{#uniqueItems}}){{/uniqueItems}}!.join(runtime.COLLECTION_FORMATS["{{collectionFormat}}"]); @@ -262,6 +276,7 @@ export class {{classname}} extends runtime.BaseAPI { } {{/isArray}} + {{/jsonHeaderUsesModelSerializer}} {{/headerParams}} {{#authMethods}} {{#isBasic}} diff --git a/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache b/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache index 58ca2968a407..65a534ef740e 100644 --- a/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache @@ -452,7 +452,7 @@ export function parseDateTime(value: any): Date { {{/isDateLibraryDate}} {{/isDateLibraryTemporal}} -{{^withoutRuntimeChecks}} +{{#modelSerializersEnabled}} export function mapValues(data: any, fn: (item: any) => any) { const result: { [key: string]: any } = {}; for (const key of Object.keys(data)) { @@ -465,7 +465,7 @@ export function mapValues(data: any, fn: (item: any) => any) { export function anyToJSON(value: any): any { return value; } -{{/withoutRuntimeChecks}} +{{/modelSerializersEnabled}} export function canConsumeForm(consumes: Consume[]): boolean { for (const consume of consumes) { diff --git a/modules/openapi-generator/src/main/resources/typescript-node/api-single.mustache b/modules/openapi-generator/src/main/resources/typescript-node/api-single.mustache index 1dfd236f7a1d..1bc9baf96fb3 100644 --- a/modules/openapi-generator/src/main/resources/typescript-node/api-single.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-node/api-single.mustache @@ -190,7 +190,14 @@ export class {{classname}} { {{/queryParams}} {{#headerParams}} +{{#headerIsJsonMimeType}} + if ({{paramName}} !== undefined) { + localVarHeaderParams['{{baseName}}'] = JSON.stringify(ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}")); + } +{{/headerIsJsonMimeType}} +{{^headerIsJsonMimeType}} localVarHeaderParams['{{baseName}}'] = ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}"); +{{/headerIsJsonMimeType}} {{/headerParams}} (Object).assign(localVarHeaderParams, options.headers); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java index bd92f9d4617d..082428b6870e 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java @@ -33,6 +33,100 @@ @Test(groups = {TypeScriptGroups.TYPESCRIPT, TypeScriptGroups.TYPESCRIPT_FETCH}) public class TypeScriptFetchClientCodegenTest { + @Test + public void testJsonContentHeaders() throws IOException { + File output = Files.createTempDirectory("typescript-fetch-json-header").toFile(); + output.deleteOnExit(); + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-fetch") + .setInputSpec("src/test/resources/3_1/typescript-json-header.yaml") + .setOutputDir(output.getAbsolutePath()); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate().forEach(File::deleteOnExit); + + Path api = output.toPath().resolve("apis/DefaultApi.ts"); + TestUtils.assertFileContains(api, + "if (requestParameters['xOptionalJsonArg'] !== undefined) {", + "headerParameters['X-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xJsonArg']));", + "headerParameters['X-Optional-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xOptionalJsonArg']));", + "headerParameters['X-Vendor-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xVendorArg']));", + "headerParameters['X-Referenced-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xReferencedJsonArg']));", + "headerParameters['X-Plain-Arg'] = String(requestParameters['xPlainArg']);"); + TestUtils.assertFileContains(output.toPath().resolve("models/HeaderArg.ts"), + "filePath?: string;", + "'file_path': value['filePath'],", + "if (value == null) {", + "return value;"); + TestUtils.assertFileNotContains(api, + "headerParameters['X-Json-Arg'] = String(", + "headerParameters['X-Optional-Json-Arg'] = String(", + "headerParameters['X-Vendor-Arg'] = String("); + } + + @Test + public void testJsonContentHeadersWithoutRuntimeChecks() throws IOException { + File output = Files.createTempDirectory("typescript-fetch-json-header").toFile(); + output.deleteOnExit(); + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-fetch") + .setInputSpec("src/test/resources/3_1/typescript-json-header.yaml") + .setOutputDir(output.getAbsolutePath()) + .addAdditionalProperty("withoutRuntimeChecks", true); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate().forEach(File::deleteOnExit); + + Path api = output.toPath().resolve("apis/DefaultApi.ts"); + TestUtils.assertFileContains(api, + "if (requestParameters['xOptionalJsonArg'] !== undefined) {", + "headerParameters['X-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xJsonArg']));", + "headerParameters['X-Optional-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xOptionalJsonArg']));", + "headerParameters['X-Vendor-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xVendorArg']));", + "headerParameters['X-Referenced-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xReferencedJsonArg']));", + "headerParameters['X-Plain-Arg'] = String(requestParameters['xPlainArg']);", + "import { HeaderArgToJSON } from '../models/HeaderArg';"); + TestUtils.assertFileNotContains(api, + "JSON.stringify(requestParameters['xJsonArg'])", + "JSON.stringify(requestParameters['xOptionalJsonArg'])"); + TestUtils.assertFileContains(output.toPath().resolve("models/HeaderArg.ts"), + "'file_path': value['filePath'],"); + } + + @Test + public void testJsonContentHeaderWebhook() throws IOException { + File output = Files.createTempDirectory("typescript-fetch-json-header-webhook").toFile(); + output.deleteOnExit(); + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-fetch") + .setInputSpec("src/test/resources/3_1/typescript-json-header-webhook.yaml") + .setOutputDir(output.getAbsolutePath()); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate().forEach(File::deleteOnExit); + + Path api = output.toPath().resolve("apis/DefaultApi.ts"); + TestUtils.assertFileContains(api, + "headerParameters['X-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xJsonArg']));", + "type HeaderArg,", + "HeaderArgToJSON,", + "} from '../models/HeaderArg';"); + TestUtils.assertFileNotContains(api, "type ,"); + } + + @Test + public void testJsonContentHeaderWebhookWithoutRuntimeChecks() throws IOException { + File output = Files.createTempDirectory("typescript-fetch-json-header-webhook").toFile(); + output.deleteOnExit(); + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-fetch") + .setInputSpec("src/test/resources/3_1/typescript-json-header-webhook.yaml") + .setOutputDir(output.getAbsolutePath()) + .addAdditionalProperty("withoutRuntimeChecks", true); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate().forEach(File::deleteOnExit); + + Path api = output.toPath().resolve("apis/DefaultApi.ts"); + TestUtils.assertFileContains(api, + "headerParameters['X-Json-Arg'] = JSON.stringify(HeaderArgToJSON(requestParameters['xJsonArg']));", + "import { HeaderArgToJSON } from '../models/HeaderArg';"); + TestUtils.assertFileContains(output.toPath().resolve("models/HeaderArg.ts"), + "'file_path': value['filePath'],"); + } + @Test public void testSnapshotVersion() { OpenAPI api = TestUtils.createOpenAPI(); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptnode/TypeScriptNodeClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptnode/TypeScriptNodeClientCodegenTest.java index 13943becb6e7..dd895c7fcbdf 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptnode/TypeScriptNodeClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptnode/TypeScriptNodeClientCodegenTest.java @@ -28,6 +28,32 @@ @Test(groups = {TypeScriptGroups.TYPESCRIPT, TypeScriptGroups.TYPESCRIPT_NODE}) public class TypeScriptNodeClientCodegenTest { + @Test + public void testJsonContentHeaders() throws IOException { + File output = Files.createTempDirectory(Paths.get("target"), "typescript-node-json-header").toFile(); + output.deleteOnExit(); + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-node") + .setInputSpec("src/test/resources/3_1/typescript-json-header.yaml") + .setOutputDir(output.getAbsolutePath()); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate().forEach(File::deleteOnExit); + + java.nio.file.Path api = output.toPath().resolve("api/defaultApi.ts"); + TestUtils.assertFileContains(api, + "if (xOptionalJsonArg !== undefined) {", + "localVarHeaderParams['X-Json-Arg'] = JSON.stringify(ObjectSerializer.serialize(xJsonArg, \"HeaderArg\"));", + "localVarHeaderParams['X-Optional-Json-Arg'] = JSON.stringify(ObjectSerializer.serialize(xOptionalJsonArg, \"HeaderArg\"));", + "localVarHeaderParams['X-Vendor-Arg'] = JSON.stringify(ObjectSerializer.serialize(xVendorArg, \"HeaderArg\"));", + "localVarHeaderParams['X-Plain-Arg'] = ObjectSerializer.serialize(xPlainArg, \"string\");"); + TestUtils.assertFileNotContains(api, + "localVarHeaderParams['X-Json-Arg'] = ObjectSerializer.serialize(", + "localVarHeaderParams['X-Optional-Json-Arg'] = ObjectSerializer.serialize(", + "localVarHeaderParams['X-Vendor-Arg'] = ObjectSerializer.serialize("); + TestUtils.assertFileContains(output.toPath().resolve("model/headerArg.ts"), + "'filePath'?: string;", + "\"baseName\": \"file_path\""); + } + private TypeScriptNodeClientCodegen codegen; @BeforeMethod diff --git a/modules/openapi-generator/src/test/resources/3_1/typescript-json-header-webhook.yaml b/modules/openapi-generator/src/test/resources/3_1/typescript-json-header-webhook.yaml new file mode 100644 index 000000000000..207176d96774 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/typescript-json-header-webhook.yaml @@ -0,0 +1,26 @@ +openapi: 3.1.0 +info: + title: JSON header webhook + version: 1.0.0 +webhooks: + onEvent: + post: + operationId: onEvent + parameters: + - name: X-Json-Arg + in: header + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/HeaderArg' + responses: + '204': + description: No content +components: + schemas: + HeaderArg: + type: object + properties: + file_path: + type: string diff --git a/modules/openapi-generator/src/test/resources/3_1/typescript-json-header.yaml b/modules/openapi-generator/src/test/resources/3_1/typescript-json-header.yaml new file mode 100644 index 000000000000..af262fa989bb --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/typescript-json-header.yaml @@ -0,0 +1,68 @@ +openapi: 3.1.0 +info: + title: JSON header serialization + version: 1.0.0 +paths: + /json: + get: + operationId: getJson + parameters: + - name: X-Json-Arg + in: header + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/HeaderArg' + - name: X-Optional-Json-Arg + in: header + content: + application/json: + schema: + $ref: '#/components/schemas/HeaderArg' + - name: X-Plain-Arg + in: header + required: true + schema: + type: string + responses: + '204': + description: No content + /vendor: + get: + operationId: getVendor + parameters: + - name: X-Vendor-Arg + in: header + required: true + content: + application/vnd.example+json: + schema: + $ref: '#/components/schemas/HeaderArg' + responses: + '204': + description: No content + /referenced: + get: + operationId: getReferenced + parameters: + - $ref: '#/components/parameters/JsonHeader' + responses: + '204': + description: No content +components: + parameters: + JsonHeader: + name: X-Referenced-Json-Arg + in: header + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/HeaderArg' + schemas: + HeaderArg: + type: object + properties: + file_path: + type: string