diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java
index 66f4d5ca6d21..dffb723a7e36 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java
@@ -17,6 +17,8 @@
package org.openapitools.codegen.languages;
+import io.swagger.v3.core.util.Json31;
+import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import lombok.Getter;
@@ -35,7 +37,10 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.TreeSet;
+import java.util.stream.Collectors;
/**
*
Mustache templates are located in {@code src/main/resources/typescript-axios/}.
@@ -74,6 +79,8 @@ public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodege
public TypeScriptAxiosClientCodegen() {
super();
+ openapiNormalizer.put("NORMALIZER_CLASS", PrimitiveCompositionNormalizer.class.getName());
+
modifyFeatureSet(features -> features
.includeDocumentationFeatures(DocumentationFeature.Readme)
.includeSecurityFeatures(SecurityFeature.BearerToken, SecurityFeature.AWSV4Signature));
@@ -249,6 +256,120 @@ public Map postProcessAllModels(Map objs)
return result;
}
+ @Override
+ public String getSchemaType(Schema schema) {
+ if (isPrimitiveWithValidationOnlyComposition(schema)) {
+ return super.getSchemaType(new Schema().type(primitiveCompositionType(schema)).format(schema.getFormat()));
+ }
+ return super.getSchemaType(schema);
+ }
+
+ @Override
+ public CodegenModel fromModel(String name, Schema schema) {
+ CodegenModel model = super.fromModel(name, schema);
+ Schema primitive = ModelUtils.getReferencedSchema(openAPI, schema);
+ if (!ModelUtils.hasProperties(schema) && schema.getAllOf() != null && schema.getAllOf().size() == 1
+ && schema.getAnyOf() == null && schema.getOneOf() == null
+ && ModelUtils.getType(schema) == null && schema.getEnum() == null
+ && schema.getConst() == null && schema.getNot() == null) {
+ // Normalization wraps nullable references in a single-member allOf.
+ primitive = ModelUtils.getReferencedSchema(openAPI, (Schema) schema.getAllOf().get(0));
+ }
+ if (isPrimitiveWithValidationOnlyComposition(primitive)) {
+ model.vendorExtensions.put("x-typescript-primitive-type", getSchemaType(primitive));
+ }
+ return model;
+ }
+
+ private static boolean isPrimitiveWithValidationOnlyComposition(Schema schema) {
+ if (primitiveCompositionType(schema) == null) {
+ return false;
+ }
+ return schema.getEnum() == null && schema.getConst() == null;
+ }
+
+ private static String primitiveCompositionType(Schema schema) {
+ if (schema == null || schema.get$ref() != null || schema.getNot() != null || schema.getIf() != null
+ || schema.getAllOf() != null || (schema.getAnyOf() != null && schema.getOneOf() != null)) {
+ return null;
+ }
+ Set types = schema.getTypes() != null ? schema.getTypes()
+ : schema.getType() == null ? Set.of() : Set.of(schema.getType());
+ List nonNullTypes = types.stream().filter(type -> !"null".equals(type)).collect(Collectors.toList());
+ if (nonNullTypes.size() != 1 || !Set.of("string", "integer", "number", "boolean").contains(nonNullTypes.get(0))) {
+ return null;
+ }
+ List alternatives = schema.getAnyOf() != null ? schema.getAnyOf() : schema.getOneOf();
+ if (alternatives == null || alternatives.isEmpty() || alternatives.stream().noneMatch(
+ TypeScriptAxiosClientCodegen::isTypeSpecificPredicate)
+ || !alternatives.stream().allMatch(branch -> isTypeSpecificPredicate(branch) || isExplicitNull(branch))) {
+ return null;
+ }
+ return nonNullTypes.get(0);
+ }
+
+ // These assertions apply only to strings, numbers, arrays or objects, so an
+ // untyped branch containing them always accepts null. In particular, enum,
+ // const, references and logical applicators must not enter this fallback.
+ private static final Set TYPE_SPECIFIC_PREDICATES = Set.of(
+ "pattern", "minLength", "maxLength", "format", "minimum", "maximum",
+ "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "minItems", "maxItems",
+ "uniqueItems", "contains", "minContains", "maxContains", "items", "prefixItems",
+ "minProperties", "maxProperties", "required", "properties", "patternProperties",
+ "additionalProperties", "propertyNames", "dependentRequired", "dependentSchemas",
+ "unevaluatedItems", "unevaluatedProperties", "title", "description", "default",
+ "examples", "example", "deprecated", "readOnly", "writeOnly");
+
+ private static boolean isTypeSpecificPredicate(Schema schema) {
+ if (schema == null || schema.getBooleanSchemaValue() != null) {
+ return false;
+ }
+ var fields = Json31.mapper().valueToTree(schema).fieldNames();
+ while (fields.hasNext()) {
+ String field = fields.next();
+ if (!TYPE_SPECIFIC_PREDICATES.contains(field) && !field.startsWith("x-")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isExplicitNull(Schema schema) {
+ return schema != null && ("null".equals(schema.getType()) || Set.of("null").equals(schema.getTypes()))
+ && Json31.mapper().valueToTree(schema).size() == 1;
+ }
+
+ /** Keeps primitive validation compositions local to Axios, before generic normalization can erase their type. */
+ public static class PrimitiveCompositionNormalizer extends OpenAPINormalizer {
+ public PrimitiveCompositionNormalizer(OpenAPI openAPI, Map rules) {
+ super(openAPI, rules);
+ }
+
+ @Override
+ public Schema normalizeSchema(Schema schema, Set visitedSchemas) {
+ String type = primitiveCompositionType(schema);
+ if (type == null || schema.getConst() != null) {
+ return super.normalizeSchema(schema, visitedSchemas);
+ }
+ List alternatives = schema.getAnyOf() != null ? schema.getAnyOf() : schema.getOneOf();
+ boolean nullable = Boolean.TRUE.equals(schema.getNullable())
+ || (schema.getTypes() != null && schema.getTypes().contains("null"));
+ // Every supported branch accepts null. oneOf accepts it only when
+ // exactly one branch matches; deduplicating branches would change this.
+ schema.setNullable(nullable && (schema.getOneOf() == null || alternatives.size() == 1)
+ && (schema.getEnum() == null || schema.getEnum().stream().anyMatch(Objects::isNull)));
+ ModelUtils.setType(schema, type);
+ List predicates = alternatives.stream()
+ .filter(branch -> !isExplicitNull(branch)).collect(Collectors.toList());
+ if (schema.getAnyOf() != null) {
+ schema.setAnyOf(predicates);
+ } else {
+ schema.setOneOf(predicates);
+ }
+ return schema;
+ }
+ }
+
@Override
protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
diff --git a/modules/openapi-generator/src/main/resources/typescript-axios/api.mustache b/modules/openapi-generator/src/main/resources/typescript-axios/api.mustache
index 337e79a292e2..d97b16dd720d 100644
--- a/modules/openapi-generator/src/main/resources/typescript-axios/api.mustache
+++ b/modules/openapi-generator/src/main/resources/typescript-axios/api.mustache
@@ -23,7 +23,7 @@ import type { RequestArgs } from './base{{importFileExtension}}';
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base{{importFileExtension}}';
{{#models}}
-{{#model}}{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{^isEnum}}{{^oneOf}}{{>modelGeneric}}{{/oneOf}}{{/isEnum}}{{/model}}
+{{#model}}{{#vendorExtensions.x-typescript-primitive-type}}{{>modelGeneric}}{{/vendorExtensions.x-typescript-primitive-type}}{{^vendorExtensions.x-typescript-primitive-type}}{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{^isEnum}}{{^oneOf}}{{>modelGeneric}}{{/oneOf}}{{/isEnum}}{{/vendorExtensions.x-typescript-primitive-type}}{{/model}}
{{/models}}
{{#apiInfo}}{{#apis}}
{{>apiInner}}
diff --git a/modules/openapi-generator/src/main/resources/typescript-axios/model.mustache b/modules/openapi-generator/src/main/resources/typescript-axios/model.mustache
index 4c4d4a73bdc8..260c25d1f392 100644
--- a/modules/openapi-generator/src/main/resources/typescript-axios/model.mustache
+++ b/modules/openapi-generator/src/main/resources/typescript-axios/model.mustache
@@ -13,5 +13,5 @@ import type { {{class}} } from './{{filename}}{{importFileExtension}}';{{/oneOf}
// @ts-ignore
import type { {{class}} } from './{{filename}}{{importFileExtension}}';{{/imports}}{{/hasOneOf}}{{/hasAllOf}}{{/withSeparateModelsAndApi}}
{{#models}}{{#model}}
-{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{#allOf}}{{#-first}}{{>modelAllOf}}{{/-first}}{{/allOf}}{{^isEnum}}{{^oneOf}}{{^allOf}}{{>modelGeneric}}{{/allOf}}{{/oneOf}}{{/isEnum}}
+{{#vendorExtensions.x-typescript-primitive-type}}{{>modelGeneric}}{{/vendorExtensions.x-typescript-primitive-type}}{{^vendorExtensions.x-typescript-primitive-type}}{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{#allOf}}{{#-first}}{{>modelAllOf}}{{/-first}}{{/allOf}}{{^isEnum}}{{^oneOf}}{{^allOf}}{{>modelGeneric}}{{/allOf}}{{/oneOf}}{{/isEnum}}{{/vendorExtensions.x-typescript-primitive-type}}
{{/model}}{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/typescript-axios/modelGeneric.mustache b/modules/openapi-generator/src/main/resources/typescript-axios/modelGeneric.mustache
index 0eab9701aa38..2cc8ab5e304a 100644
--- a/modules/openapi-generator/src/main/resources/typescript-axios/modelGeneric.mustache
+++ b/modules/openapi-generator/src/main/resources/typescript-axios/modelGeneric.mustache
@@ -3,6 +3,10 @@
* {{{description}}}
*/
{{/description}}
+{{#vendorExtensions.x-typescript-primitive-type}}
+export type {{classname}} = {{{.}}}{{#isNullable}} | null{{/isNullable}};
+{{/vendorExtensions.x-typescript-primitive-type}}
+{{^vendorExtensions.x-typescript-primitive-type}}
export interface {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{
{{#additionalPropertiesType}}
[key: string]: {{{additionalPropertiesType}}}{{#additionalPropertiesIsAnyType}}{{#hasVars}} | any{{/hasVars}}{{/additionalPropertiesIsAnyType}};
@@ -63,3 +67,4 @@ export type {{enumName}} = typeof {{enumName}}[keyof typeof {{enumName}}];
{{/isEnum}}
{{/vars}}
{{/hasEnums}}
+{{/vendorExtensions.x-typescript-primitive-type}}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java
index a7f237f756f5..eb1ebba6f421 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java
@@ -1,7 +1,14 @@
package org.openapitools.codegen.typescript.axios;
+import com.fasterxml.jackson.databind.node.NullNode;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.ComposedSchema;
+import io.swagger.v3.oas.models.media.JsonSchema;
+import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenConstants;
+import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.TestUtils;
@@ -9,12 +16,15 @@
import org.openapitools.codegen.languages.TypeScriptAxiosClientCodegen;
import org.openapitools.codegen.typescript.TypeScriptGroups;
import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.HashSet;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.assertEquals;
@@ -24,6 +34,116 @@ public class TypeScriptAxiosClientCodegenTest {
TypeScriptAxiosClientCodegen codegen = new TypeScriptAxiosClientCodegen();
+ @Test
+ public void testParsedNullConstDoesNotUsePrimitiveFallback() throws Exception {
+ final File output = Files.createTempDirectory("typescript_axios_null_const_").toFile();
+ output.deleteOnExit();
+ final ClientOptInput input = new CodegenConfigurator()
+ .setGeneratorName("typescript-axios")
+ .setInputSpec("src/test/resources/3_1/typescript-axios/primitive-const-null.yaml")
+ .setOutputDir(output.getAbsolutePath().replace("\\", "/"))
+ .toClientOptInput();
+ // The parser represents an explicit const: null as NullNode, not Java null.
+ for (String name : List.of("NullOnly", "Impossible")) {
+ assertThat(input.getOpenAPI().getComponents().getSchemas().get(name).getConst())
+ .isEqualTo(NullNode.getInstance());
+ }
+ new DefaultGenerator().opts(input).generate().forEach(File::deleteOnExit);
+ for (String name : List.of("NullOnly", "Impossible")) {
+ assertThat(input.getOpenAPI().getComponents().getSchemas().get(name).getConst())
+ .isEqualTo(NullNode.getInstance());
+ }
+ Path api = output.toPath().resolve("api.ts");
+ TestUtils.assertFileContains(api, "export type Unconstrained = string | null;");
+ // General const type generation is outside this fallback's scope.
+ TestUtils.assertFileNotContains(api, "export type NullOnly = string", "export type Impossible = string");
+ }
+
+ @Test
+ public void testPrimitiveNormalizationRetainsSiblingConstraints() {
+ Schema predicate = new Schema().pattern("^[a-z]+$");
+ Schema schema = new ComposedSchema().type("string").nullable(true)
+ ._enum(List.of("allowed")).minLength(2);
+ schema.setAnyOf(List.of(predicate));
+ Schema normalized = new TypeScriptAxiosClientCodegen.PrimitiveCompositionNormalizer(new OpenAPI(), Map.of())
+ .normalizeSchema(schema, new HashSet<>());
+ assertThat(normalized.getType()).isEqualTo("string");
+ assertThat(normalized.getEnum()).containsExactly("allowed");
+ assertThat(normalized.getMinLength()).isEqualTo(2);
+ assertThat(normalized.getAnyOf()).containsExactly(predicate);
+ assertThat(normalized.getNullable()).isFalse();
+ }
+
+ @Test
+ public void testPrimitiveAnyOfFallbackSkipsOtherTypeConstraints() {
+ Schema enumSchema = new ComposedSchema().type("string")._enum(List.of("allowed"));
+ Schema constSchema = new JsonSchema()._const("allowed");
+ constSchema.addType("string");
+ Schema mixedTypes = new JsonSchema();
+ mixedTypes.addType("string");
+ mixedTypes.addType("number");
+ for (Schema schema : List.of(enumSchema, constSchema, mixedTypes)) {
+ schema.addAnyOfItem(new Schema().pattern("^[a-z]+$"));
+ TypeScriptAxiosClientCodegen generator = new TypeScriptAxiosClientCodegen();
+ generator.setOpenAPI(new OpenAPI().components(new Components().addSchemas("Constrained", schema)));
+ CodegenModel model = generator.fromModel("Constrained", schema);
+ assertThat(model.vendorExtensions).doesNotContainKey("x-typescript-primitive-type");
+ if (schema == enumSchema) {
+ assertThat(model.allowableValues).containsEntry("values", List.of("allowed"));
+ }
+ }
+ }
+
+ @DataProvider
+ public Object[][] primitiveAnyOfOptions() {
+ return new Object[][] {{"3_0", false}, {"3_0", true}, {"3_1", false}, {"3_1", true}};
+ }
+
+ @Test(dataProvider = "primitiveAnyOfOptions")
+ public void testPrimitiveAnyOfValidation(String version, boolean separateModels) throws Exception {
+ final File output = Files.createTempDirectory("typescript_axios_primitive_anyof_").toFile();
+ output.deleteOnExit();
+ final CodegenConfigurator configurator = new CodegenConfigurator()
+ .setGeneratorName("typescript-axios")
+ .setInputSpec("src/test/resources/" + version + "/typescript-axios/primitive-anyof-validation.yaml")
+ .addAdditionalProperty("withSeparateModelsAndApi", separateModels)
+ .addAdditionalProperty("apiPackage", "api")
+ .addAdditionalProperty("modelPackage", "model")
+ .setOutputDir(output.getAbsolutePath().replace("\\", "/"));
+ new DefaultGenerator().opts(configurator.toClientOptInput()).generate().forEach(File::deleteOnExit);
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/hostname.ts" : "api.ts"),
+ "export type Hostname = string;");
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/nullable-hostname.ts" : "api.ts"),
+ "export type NullableHostname = string | null;");
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/single-pattern.ts" : "api.ts"),
+ "export type SinglePattern = string;");
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/exclusive-pattern.ts" : "api.ts"),
+ "export type ExclusivePattern = string;");
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/port.ts" : "api.ts"),
+ "export type Port = number;");
+ Path address = output.toPath().resolve(separateModels ? "model/address.ts" : "api.ts");
+ TestUtils.assertFileContains(address, "'hostname': Hostname;");
+ TestUtils.assertFileContains(address, "'exclusivePattern'?: ExclusivePattern;");
+ TestUtils.assertFileNotContains(address, "'hostname': Hostname | null;", "export interface Hostname");
+ if ("3_1".equals(version)) {
+ for (String[] expected : List.of(
+ new String[] {"contains-string", "ContainsString", "string"},
+ new String[] {"null-first-hostname", "NullFirstHostname", "string | null"},
+ new String[] {"nullable-exclusive-pattern", "NullableExclusivePattern", "string"},
+ new String[] {"exclusive-pattern-or-null", "ExclusivePatternOrNull", "string"})) {
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/" + expected[0] + ".ts" : "api.ts"),
+ "export type " + expected[1] + " = " + expected[2] + ";");
+ }
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/enum-pattern.ts" : "api.ts"),
+ "Allowed: 'allowed'");
+ TestUtils.assertFileContains(address, "'nullableAlias'?: NullableAlias | null;");
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "model/nullable-alias.ts" : "api.ts"),
+ "export type NullableAlias = string | null;");
+ }
+ TestUtils.assertFileContains(output.toPath().resolve(separateModels ? "api/default-api.ts" : "api.ts"),
+ "createAddress(address: Address, options?: RawAxiosRequestConfig)");
+ }
+
@Test
public void testToEnumVarNameOriginalNamingType() {
codegen.additionalProperties().put(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.original.name());
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 f08934d4dc42..cf2f25efaa82 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
@@ -35,6 +35,21 @@
@Test(groups = {TypeScriptGroups.TYPESCRIPT, TypeScriptGroups.TYPESCRIPT_FETCH})
public class TypeScriptFetchClientCodegenTest {
+ @Test
+ public void testPrimitiveCompositionSerialization() throws IOException {
+ File output = Files.createTempDirectory("typescript-fetch-primitive-composition").toFile();
+ output.deleteOnExit();
+ CodegenConfigurator configurator = new CodegenConfigurator()
+ .setGeneratorName("typescript-fetch")
+ .setInputSpec("src/test/resources/3_1/primitive-composition-runtime.yaml")
+ .setOutputDir(output.getAbsolutePath());
+ new DefaultGenerator().opts(configurator.toClientOptInput()).generate().forEach(File::deleteOnExit);
+ // A validation-only oneOf must not route a scalar through an empty-object
+ // model converter, which would silently encode and decode "letters" as {}.
+ TestUtils.assertFileContains(output.toPath().resolve("models/Envelope.ts"),
+ "'value': json['value'],", "'value': value['value'],");
+ }
+
@Test
public void testJsonContentHeaders() throws IOException {
File output = Files.createTempDirectory("typescript-fetch-json-header").toFile();
diff --git a/modules/openapi-generator/src/test/resources/3_0/typescript-axios/primitive-anyof-validation.yaml b/modules/openapi-generator/src/test/resources/3_0/typescript-axios/primitive-anyof-validation.yaml
new file mode 100644
index 000000000000..1fb9ce8541b5
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/typescript-axios/primitive-anyof-validation.yaml
@@ -0,0 +1,71 @@
+openapi: 3.0.3
+info:
+ title: Primitive anyOf validation
+ version: 1.0.0
+paths:
+ /addresses:
+ post:
+ operationId: createAddress
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Address'
+ responses:
+ '200':
+ description: Address
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Address'
+components:
+ schemas:
+ Hostname:
+ type: string
+ description: A hostname or wildcard hostname.
+ anyOf:
+ - pattern: '^[a-z]+[.]example$'
+ - pattern: '^[*][.][a-z]+[.]example$'
+ NullableHostname:
+ type: string
+ nullable: true
+ anyOf:
+ - pattern: '^[a-z]+[.]example$'
+ - pattern: '^[*][.][a-z]+[.]example$'
+ SinglePattern:
+ type: string
+ minLength: 2
+ anyOf:
+ - pattern: '^[a-z]+$'
+ ExclusivePattern:
+ type: string
+ oneOf:
+ - pattern: '^[a-z]+$'
+ - pattern: '^[0-9]+$'
+ Port:
+ type: integer
+ anyOf:
+ - minimum: 1000
+ maximum: 2000
+ - minimum: 8000
+ maximum: 9000
+ Address:
+ type: object
+ required: [hostname, nullableHostname, label, port, inlineHostname]
+ properties:
+ exclusivePattern:
+ $ref: '#/components/schemas/ExclusivePattern'
+ hostname:
+ $ref: '#/components/schemas/Hostname'
+ nullableHostname:
+ $ref: '#/components/schemas/NullableHostname'
+ label:
+ $ref: '#/components/schemas/SinglePattern'
+ port:
+ $ref: '#/components/schemas/Port'
+ inlineHostname:
+ type: string
+ anyOf:
+ - pattern: '^[a-z]+[.]example$'
+ - pattern: '^[*][.][a-z]+[.]example$'
diff --git a/modules/openapi-generator/src/test/resources/3_1/primitive-composition-runtime.yaml b/modules/openapi-generator/src/test/resources/3_1/primitive-composition-runtime.yaml
new file mode 100644
index 000000000000..4d1e8ec209cf
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_1/primitive-composition-runtime.yaml
@@ -0,0 +1,34 @@
+openapi: 3.1.0
+info:
+ title: Primitive composition runtime
+ version: '1'
+paths:
+ /values:
+ post:
+ operationId: createEnvelope
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Envelope'
+ responses:
+ '200':
+ description: Value
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Envelope'
+components:
+ schemas:
+ Envelope:
+ type: object
+ required: [value]
+ properties:
+ value:
+ $ref: '#/components/schemas/PatternValue'
+ PatternValue:
+ type: string
+ oneOf:
+ - pattern: '^[a-z]+$'
+ - pattern: '^[0-9]+$'
diff --git a/modules/openapi-generator/src/test/resources/3_1/typescript-axios/primitive-anyof-validation.yaml b/modules/openapi-generator/src/test/resources/3_1/typescript-axios/primitive-anyof-validation.yaml
new file mode 100644
index 000000000000..8fc00af4fe9b
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_1/typescript-axios/primitive-anyof-validation.yaml
@@ -0,0 +1,101 @@
+openapi: 3.1.0
+info:
+ title: Primitive anyOf validation
+ version: 1.0.0
+paths:
+ /addresses:
+ post:
+ operationId: createAddress
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Address'
+ responses:
+ '200':
+ description: Address
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Address'
+components:
+ schemas:
+ Hostname:
+ type: string
+ description: A hostname or wildcard hostname.
+ anyOf:
+ - pattern: '^[a-z]+[.]example$'
+ - pattern: '^[*][.][a-z]+[.]example$'
+ NullableHostname:
+ type: [string, 'null']
+ anyOf:
+ - pattern: '^[a-z]+[.]example$'
+ - pattern: '^[*][.][a-z]+[.]example$'
+ SinglePattern:
+ type: string
+ minLength: 2
+ anyOf:
+ - pattern: '^[a-z]+$'
+ ExclusivePattern:
+ type: string
+ oneOf:
+ - pattern: '^[a-z]+$'
+ - pattern: '^[0-9]+$'
+ Port:
+ type: integer
+ anyOf:
+ - minimum: 1000
+ maximum: 2000
+ - minimum: 8000
+ maximum: 9000
+ ContainsString:
+ type: string
+ anyOf:
+ - contains:
+ type: integer
+ - type: 'null'
+ NullFirstHostname:
+ type: ['null', string]
+ anyOf:
+ - pattern: '^[a-z]+$'
+ NullableExclusivePattern:
+ type: [string, 'null']
+ oneOf:
+ - pattern: '^[a-z]+$'
+ - pattern: '^[0-9]+$'
+ ExclusivePatternOrNull:
+ type: [string, 'null']
+ oneOf:
+ - pattern: '^[a-z]+$'
+ - type: 'null'
+ EnumPattern:
+ type: string
+ enum: [allowed]
+ anyOf:
+ - pattern: '^[a-z]+$'
+ NullableAlias:
+ anyOf:
+ - $ref: '#/components/schemas/Hostname'
+ - type: 'null'
+ Address:
+ type: object
+ required: [hostname, nullableHostname, label, port, inlineHostname]
+ properties:
+ exclusivePattern:
+ $ref: '#/components/schemas/ExclusivePattern'
+ hostname:
+ $ref: '#/components/schemas/Hostname'
+ nullableAlias:
+ $ref: '#/components/schemas/NullableAlias'
+ nullableHostname:
+ $ref: '#/components/schemas/NullableHostname'
+ label:
+ $ref: '#/components/schemas/SinglePattern'
+ port:
+ $ref: '#/components/schemas/Port'
+ inlineHostname:
+ type: string
+ anyOf:
+ - pattern: '^[a-z]+[.]example$'
+ - pattern: '^[*][.][a-z]+[.]example$'
diff --git a/modules/openapi-generator/src/test/resources/3_1/typescript-axios/primitive-const-null.yaml b/modules/openapi-generator/src/test/resources/3_1/typescript-axios/primitive-const-null.yaml
new file mode 100644
index 000000000000..715e5345b296
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_1/typescript-axios/primitive-const-null.yaml
@@ -0,0 +1,21 @@
+openapi: 3.1.0
+info:
+ title: Primitive const null
+ version: '1'
+paths: {}
+components:
+ schemas:
+ NullOnly:
+ type: [string, 'null']
+ const: null
+ anyOf:
+ - pattern: '^[a-z]+$'
+ Impossible:
+ type: string
+ const: null
+ anyOf:
+ - pattern: '^[a-z]+$'
+ Unconstrained:
+ type: [string, 'null']
+ anyOf:
+ - pattern: '^[a-z]+$'