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]+$'