diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 7e88bfab6869..cb12395c6fc0 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -35,7 +35,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Login to DockerHub - uses: docker/login-action@v4 + uses: docker/login-action@v4.5.2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/samples-java-sbt.yaml b/.github/workflows/samples-java-sbt.yaml index 9dec0ab90dae..12609fc48b5b 100644 --- a/.github/workflows/samples-java-sbt.yaml +++ b/.github/workflows/samples-java-sbt.yaml @@ -24,7 +24,7 @@ jobs: distribution: 'temurin' java-version: 17 - name: Setup sbt launcher - uses: sbt/setup-sbt@v1 + uses: sbt/setup-sbt@v1.5.4 - name: Cache maven dependencies uses: actions/cache@v6 env: diff --git a/.github/workflows/samples-scala-client.yaml b/.github/workflows/samples-scala-client.yaml index 2cf0647dadb9..4a0a48c13def 100644 --- a/.github/workflows/samples-scala-client.yaml +++ b/.github/workflows/samples-scala-client.yaml @@ -59,7 +59,7 @@ jobs: distribution: 'temurin' java-version: 11 - name: Setup sbt launcher - uses: sbt/setup-sbt@v1 + uses: sbt/setup-sbt@v1.5.4 - name: Cache maven dependencies uses: actions/cache@v6 env: diff --git a/.github/workflows/samples-scala-server.yaml b/.github/workflows/samples-scala-server.yaml index cd89bb461a26..f412eb1cd21a 100644 --- a/.github/workflows/samples-scala-server.yaml +++ b/.github/workflows/samples-scala-server.yaml @@ -29,7 +29,7 @@ jobs: distribution: 'temurin' java-version: 11 - name: Setup sbt launcher - uses: sbt/setup-sbt@v1 + uses: sbt/setup-sbt@v1.5.4 - name: Cache maven dependencies uses: actions/cache@v6 env: 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 8f6ec1a067ea..19eea4bcdeea 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 @@ -1014,7 +1014,7 @@ public boolean specVersionGreaterThanOrEqualTo310(OpenAPI openAPI) { @Override public void setOpenAPI(OpenAPI openAPI) { if (specVersionGreaterThanOrEqualTo310(openAPI)) { - LOGGER.warn(UNSUPPORTED_V310_SPEC_MSG); + once(LOGGER).warn(UNSUPPORTED_V310_SPEC_MSG); } this.openAPI = openAPI; // Set global settings such that helper functions in ModelUtils can lookup the value @@ -1264,7 +1264,7 @@ public String encodePath(String input) { */ @Override public String escapeUnsafeCharacters(String input) { - LOGGER.warn("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " + + once(LOGGER).warn("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " + "unsafe characters"); // doing nothing by default and code generator should implement // the logic to prevent code injection @@ -1281,7 +1281,7 @@ public String escapeUnsafeCharacters(String input) { */ @Override public String escapeQuotationMark(String input) { - LOGGER.warn("escapeQuotationMark should be overridden in the code generator with proper logic to escape " + + once(LOGGER).warn("escapeQuotationMark should be overridden in the code generator with proper logic to escape " + "single/double quote"); return input.replace("\"", "\\\""); } @@ -2924,7 +2924,7 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map imports) codegenParameter.setTypeProperties(parameterSchema, openAPI); codegenParameter.setComposedSchemas(getComposedSchemas(parameterSchema)); - if (Boolean.TRUE.equals(parameterSchema.getNullable())) { // use nullable defined in the spec + if (ModelUtils.isNullable(parameterSchema)) { // use nullable defined in the spec codegenParameter.isNullable = true; } @@ -8060,7 +8054,9 @@ public CodegenParameter fromRequestBody(RequestBody body, Set imports, S if (original.getNullable() != null) { codegenParameter.isNullable = original.getNullable(); } else if (original.getExtensions() != null && original.getExtensions().containsKey(X_NULLABLE)) { - codegenParameter.isNullable = (Boolean) original.getExtensions().get(X_NULLABLE); + codegenParameter.isNullable = Boolean.parseBoolean(String.valueOf(original.getExtensions().get(X_NULLABLE))); + } else if (ModelUtils.isNullable(original)) { + codegenParameter.isNullable = true; } if (original.getExtensions() != null) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index 9a304b529503..d0b71a23d1c3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -998,8 +998,8 @@ public Schema normalizeSchema(Schema schema, Set visitedSchemas) { } normalizeProperties(schema, visitedSchemas); } else if (schema.getAdditionalProperties() instanceof Schema) { // map - normalizeMapSchema(schema); - Schema additionalProperties = (Schema) schema.getAdditionalProperties(); + Schema result = normalizeMapSchema(schema); + Schema additionalProperties = (Schema) result.getAdditionalProperties(); if (getRule(NORMALIZE_31SPEC) && ModelUtils.isNullTypeSchema(openAPI, additionalProperties)) { // OAS 3.1 allows a map value schema of `type: "null"` (e.g. // `additionalProperties: { type: "null" }`). There's no OAS 3.0 equivalent type, @@ -1008,15 +1008,17 @@ public Schema normalizeSchema(Schema schema, Set visitedSchemas) { // generated as a normal (nullable) object instead. Schema anyTypeNullable = new Schema(); anyTypeNullable.setNullable(true); - schema.setAdditionalProperties(anyTypeNullable); + result.setAdditionalProperties(anyTypeNullable); } else { Schema normalized = normalizeSchema(additionalProperties, visitedSchemas); if (getRule(NORMALIZE_31SPEC)) { // capture the normalized value schema (e.g. an OAS 3.1 `type: [array, "null"]` // value is rewritten to a proper array schema), which would otherwise be lost. - schema.setAdditionalProperties(normalized); + result.setAdditionalProperties(normalized); } } + + return result; } else if (schema instanceof BooleanSchema) { normalizeBooleanSchema(schema, visitedSchemas); } else if (schema instanceof IntegerSchema) { @@ -1105,7 +1107,8 @@ protected Schema normalizeArraySchema(Schema schema) { } protected Schema normalizeMapSchema(Schema schema) { - return processSetMapToNullable(schema); + Schema result = processNormalize31Spec(schema, new HashSet<>()); + return processSetMapToNullable(result); } protected Schema normalizeSimpleSchema(Schema schema, Set visitedSchemas) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java index b99daf9706dc..835b21ad840e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java @@ -636,6 +636,7 @@ public ModelsMap postProcessModels(ModelsMap objs) { if (useOptional) { for (ModelMap modelMap : objs.getModels()) { CodegenModel model = modelMap.getModel(); + boolean hasOptionalProperties = false; boolean shouldUseOptional; @@ -650,9 +651,12 @@ public ModelsMap postProcessModels(ModelsMap objs) { for (CodegenProperty prop : model.vars) { if (!prop.required && !prop.dataType.startsWith("Optional<")) { wrapPropertyWithOptional(prop); + hasOptionalProperties = true; } } } + + model.vendorExtensions.put("x-has-optional-properties", hasOptionalProperties); } } @@ -667,6 +671,7 @@ private void wrapPropertyWithOptional(CodegenProperty property) { boolean hasNullableSuffix = property.dataType.endsWith("?"); String baseType = hasNullableSuffix ? property.dataType.substring(0, property.dataType.length() - 1) : property.dataType; + property.vendorExtensions.put("x-unwrapped-datatype-nullable", baseType + "?"); property.dataType = "Optional<" + baseType + "?" + ">"; if (property.datatypeWithEnum != null && !property.datatypeWithEnum.startsWith("Optional<")) { @@ -674,6 +679,8 @@ private void wrapPropertyWithOptional(CodegenProperty property) { baseType = hasNullableSuffix ? property.datatypeWithEnum.substring(0, property.datatypeWithEnum.length() - 1) : property.datatypeWithEnum; property.datatypeWithEnum = "Optional<" + baseType + "?" + ">"; } + + property.isNullable = false; } @Override diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java index e99b4db285aa..28dac4989f12 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java @@ -47,6 +47,7 @@ import org.openapitools.codegen.model.ModelsMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.helpers.MessageFormatter; import java.math.BigDecimal; import java.net.URI; @@ -1297,6 +1298,39 @@ public static List getAllSchemas(OpenAPI openAPI) { return allSchemas; } + /** + * Return the list of all schemas in the entire OpenAPI document, including inline schemas + * defined in path operations (request bodies, responses, parameters, headers, callbacks) + * and schemas under components/schemas. Results are deduplicated by identity. + * This is a superset of {@link #getAllSchemas(OpenAPI)}. + * + * @param openAPI specification + * @return schemas a deduplicated list of all schemas in the document + */ + public static List getAllSchemasInDocument(OpenAPI openAPI) { + List allSchemas = new ArrayList(); + Set seen = Collections.newSetFromMap(new IdentityHashMap<>()); + + // Visit schemas reachable from paths (inline + $ref targets) + visitOpenAPI(openAPI, (s, mimeType) -> { + if (seen.add(s)) { + allSchemas.add(s); + } + }); + + // Also visit components/schemas entries not reachable from any path + List refSchemas = new ArrayList(); + getSchemas(openAPI).forEach((key, schema) -> { + visitSchema(openAPI, schema, null, refSchemas, (s, mimeType) -> { + if (seen.add(s)) { + allSchemas.add(s); + } + }); + }); + + return allSchemas; + } + /** * If a RequestBody contains a reference to another RequestBody with '$ref', returns the referenced RequestBody if it is found or the actual RequestBody in the other cases. * @@ -1588,7 +1622,7 @@ public static Schema unaliasSchema(OpenAPI openAPI, Schema ref = allSchemas.get(simpleRef); if (ref == null) { if (!isRefToSchemaWithProperties(schema.get$ref())) { - once(LOGGER).warn("{} is not defined", schema.get$ref()); + once(LOGGER).warn(MessageFormatter.format("{} is not defined", schema.get$ref()).getMessage()); } return schema; } else if (isEnumSchema(ref)) { @@ -1968,6 +2002,7 @@ public static boolean isNullable(Schema schema) { if (schema.getExtensions() != null && schema.getExtensions().get(X_NULLABLE) != null) { return Boolean.parseBoolean(schema.getExtensions().get(X_NULLABLE).toString()); } + // In OAS 3.1, the recommended way to define a nullable property or object is to use oneOf. if (isComposedSchema(schema)) { return isNullableComposedSchema(schema); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OnceLogger.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OnceLogger.java index f3a7e1bb79b4..5d7ebf521ea9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OnceLogger.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OnceLogger.java @@ -91,6 +91,12 @@ static void caffeineCache(Ticker ticker, int expireMillis) { .build(); } + /** + * This implementation currently only supports single-argument string literal log methods (e.g. {@link Logger#debug(String)}). + * + * @param logger The logger that should only log once for single-argument string literal log methods. + * @return The {@link OnceLogger} + */ public static Logger once(Logger logger) { try { if (Boolean.parseBoolean(GlobalSettings.getProperty(ENABLE_ONCE_LOGGER_PROPERTY, "true"))) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java index 47c98f557411..3405fccdf9cf 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java @@ -25,6 +25,7 @@ import org.openapitools.codegen.CodegenConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.helpers.MessageFormatter; import java.net.MalformedURLException; import java.net.URL; @@ -37,14 +38,21 @@ public class URLPathUtils { private static final Logger LOGGER = LoggerFactory.getLogger(URLPathUtils.class); + private static final String SERVER_NOT_SPECIFIED = + "'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]"; + private static final String SCHEME_NOT_DEFINED = "'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]"; + private static final String NO_SERVER_INFO = "Server information not defined in the spec. Default to {}."; + private static final String INVALID_URL = "Not a valid URL: {}. Default to {}."; public static final String LOCAL_HOST = "http://localhost"; public static final Pattern VARIABLE_PATTERN = Pattern.compile("(? userDefinedVariables) { final List servers = openAPI.getServers(); if (servers == null || servers.isEmpty()) { - once(LOGGER).warn("Server information seems not defined in the spec. Default to {}.", LOCAL_HOST); + String message = MessageFormatter.format(NO_SERVER_INFO, LOCAL_HOST).getMessage(); + once(LOGGER).warn(message); return getDefaultUrl(); } // TODO need a way to obtain all server URLs @@ -67,7 +75,8 @@ public static URL getServerURL(final Server server, final Map us try { return new URL(url); } catch (MalformedURLException e) { - once(LOGGER).warn("Not valid URL: {}. Default to {}.", server.getUrl(), LOCAL_HOST); + String malformedUrl = MessageFormatter.format(INVALID_URL, server.getUrl(), LOCAL_HOST).getMessage(); + once(LOGGER).warn(malformedUrl); } } return getDefaultUrl(); @@ -206,19 +215,22 @@ private static String sanitizeUrl(String url) { if (url != null) { if (url.startsWith("//")) { url = "http:" + url; - once(LOGGER).warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); + String missingScheme = MessageFormatter.format(SCHEME_NOT_DEFINED, url).getMessage(); + once(LOGGER).warn(missingScheme); } else if (url.startsWith("/")) { url = LOCAL_HOST + url; - once(LOGGER).info("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url); - } else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) { + String serverDefaultToLocalhost = MessageFormatter.format(SERVER_NOT_SPECIFIED, LOCAL_HOST, url).getMessage(); + once(LOGGER).info(serverDefaultToLocalhost); + } else if (!URL_WITH_SCHEME.matcher(url).matches()) { // Add http scheme for urls without a scheme. // 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss" // 3.0 spec does not have an enumerated list of schemes // This regex attempts to capture all schemes in IANA example schemes which - // can have alpha-numeric characters and [.+-]. Examples are here: + // can have alphanumeric characters and [.+-]. Examples are here: // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml url = "http://" + url; - once(LOGGER).warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); + String missingScheme = MessageFormatter.format(SCHEME_NOT_DEFINED, url).getMessage(); + once(LOGGER).warn(missingScheme); } } return url; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/OpenApiEvaluator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/OpenApiEvaluator.java index 56097b74d0fd..ced0a74cf7cc 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/OpenApiEvaluator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/OpenApiEvaluator.java @@ -56,6 +56,29 @@ public ValidationResult validate(OpenAPI specification) { validationResult.consume(schemaValidations.validate(wrapper)); }); + // Per-occurrence check: default value not in enum. + // Uses getAllSchemasInDocument to also cover inline schemas in path operations. + if (ruleConfiguration.isEnableRecommendations() + && ruleConfiguration.isEnableDefaultNotInEnumRecommendation()) { + ValidationRule defaultNotInEnumRule = ValidationRule.create(Severity.WARNING, + "Schema has default value not in enum", + "While technically valid, a default outside the enum may cause " + + "generators to emit incorrect default values.", + s -> ValidationRule.Pass.empty()); + for (Schema schema : ModelUtils.getAllSchemasInDocument(specification)) { + List enumList = schema.getEnum(); + Object defaultValue = schema.getDefault(); + if (enumList != null && !enumList.isEmpty() + && defaultValue != null + && !enumList.contains(defaultValue)) { + validationResult.addResult(Validated.invalid(defaultNotInEnumRule, + String.format(Locale.ROOT, + "Schema has default value '%s' not in enum %s", + defaultValue, enumList))); + } + } + } + List parameters = new ArrayList<>(50); Paths paths = specification.getPaths(); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/RuleConfiguration.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/RuleConfiguration.java index 623c2ac19afb..5a276e39389c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/RuleConfiguration.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/validations/oas/RuleConfiguration.java @@ -138,6 +138,20 @@ public class RuleConfiguration { * @param enableApiRequestUriWithBodyRecommendation true to enable, false to disable */ private boolean enableApiRequestUriWithBodyRecommendation = defaultedBoolean(propertyPrefix + ".anti-patterns.uri-unexpected-body", true); + /** + * -- GETTER -- + * Gets whether the recommendation check for default values not in enum is enabled. + *

+ * JSON Schema treats 'default' as an annotation keyword — it is RECOMMENDED to validate + * against the schema but not required. A default outside the enum is technically valid + * but causes generators to emit incorrect default values. + * + * @return true if enabled, false if disabled + * -- SETTER -- + * Enable or Disable the recommendation check for default values not in enum. + * @param enableDefaultNotInEnumRecommendation true to enable, false to disable + */ + private boolean enableDefaultNotInEnumRecommendation = defaultedBoolean(propertyPrefix + ".default-not-in-enum", true); @SuppressWarnings("SameParameterValue") private static boolean defaultedBoolean(String key, boolean defaultValue) { diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pojo.mustache index 2e6f060545e4..e5039c6bcd36 100644 --- a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pojo.mustache @@ -367,7 +367,12 @@ public {{>sealed}}class {{classname}} {{#parent}}extends {{{.}}}{{/parent}} {{#v public static abstract class {{classname}}Builder> {{#parent}}extends {{{.}}}Builder{{/parent}} { {{#vars}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + private JsonNullable<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}> {{name}} = JsonNullable.<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}>{{#isContainer}}undefined(){{/isContainer}}{{^isContainer}}{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}{{/isContainer}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} private {{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} {{/vars}} {{^parent}} protected abstract B self(); @@ -380,7 +385,12 @@ public {{>sealed}}class {{classname}} {{#parent}}extends {{{.}}}{{/parent}} {{#v @Deprecated {{/deprecated}} public B {{name}}({{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + this.{{name}} = JsonNullable.<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}>of({{name}}); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} this.{{name}} = {{name}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} return self(); } {{/vars}} diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/lib.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/lib.mustache index 880bb6258354..581624c15a8b 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/lib.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/lib.mustache @@ -4,6 +4,8 @@ export 'package:{{pubName}}/{{sourceFolder}}/auth/api_key_auth.dart'; export 'package:{{pubName}}/{{sourceFolder}}/auth/basic_auth.dart'; export 'package:{{pubName}}/{{sourceFolder}}/auth/bearer_auth.dart'; export 'package:{{pubName}}/{{sourceFolder}}/auth/oauth.dart'; +{{#useOptional}}export 'package:{{pubName}}/{{sourceFolder}}/optional.dart'; +{{/useOptional}} {{#useBuiltValue}}export 'package:{{pubName}}/{{sourceFolder}}/serializers.dart'; {{#useDateLibCore}}export 'package:{{pubName}}/{{sourceFolder}}/{{modelPackage}}/date.dart';{{/useDateLibCore}}{{/useBuiltValue}} diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/optional.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/optional.mustache index 9a2747da4e52..f4fa6f8f2586 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/optional.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/optional.mustache @@ -19,7 +19,7 @@ import 'package:json_annotation/json_annotation.dart'; /// // Field has value - sends {"field": "value"} /// final patch3 = Model(field: const Optional.present('value')); /// ``` -sealed class Optional { +abstract class Optional { const Optional(); /// Creates an Optional with an absent value (not set). @@ -48,7 +48,7 @@ sealed class Optional { } /// Represents an absent Optional value. -final class Absent extends Optional { +class Absent extends Optional { const Absent(); @override @@ -77,7 +77,7 @@ final class Absent extends Optional { } /// Represents a present Optional value. -final class Present extends Optional { +class Present extends Optional { const Present(this._value); final T _value; diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class.mustache index 7a9ee688596b..b7ce07ace304 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class.mustache @@ -2,6 +2,8 @@ import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart';{{#oneOf}}{{#-first}} import 'package:one_of/one_of.dart';{{/-first}}{{/oneOf}}{{#anyOf}}{{#-first}} import 'package:one_of/any_of.dart';{{/-first}}{{/anyOf}} +{{#vendorExtensions.x-has-optional-properties}}import 'package:{{pubName}}/{{sourceFolder}}/optional.dart'; +{{/vendorExtensions.x-has-optional-properties}} {{#imports}} {{/imports}} diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_members.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_members.mustache index 50b7f98ba60d..cfc50dd82b87 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_members.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_members.mustache @@ -7,7 +7,7 @@ @Deprecated('{{{name}}} has been deprecated') {{/deprecated}} @BuiltValueField(wireName: r'{{baseName}}') - {{>serialization/built_value/variable_type}}{{^isNullable}}{{^required}}?{{/required}}{{/isNullable}} get {{name}}; + {{>serialization/built_value/variable_type}}{{^vendorExtensions.x-is-optional}}{{^isNullable}}{{^required}}?{{/required}}{{/isNullable}}{{/vendorExtensions.x-is-optional}} get {{name}}; {{#allowableValues}} // {{#min}}range from {{{min}}} to {{{max}}}{{/min}}{{^min}}enum {{name}}Enum { {{#values}} {{{.}}}, {{/values}} };{{/min}} {{/allowableValues}} @@ -31,8 +31,9 @@ factory {{classname}}([void updates({{classname}}Builder b)]) = _${{classname}}; @BuiltValueHook(initializeBuilder: true) - static void _defaults({{{classname}}}Builder b) => b{{#vendorExtensions.x-parent-discriminator}}..{{propertyName}}=b.discriminatorValue{{/vendorExtensions.x-parent-discriminator}}{{#vendorExtensions.x-self-and-ancestor-only-props}}{{#defaultValue}} - ..{{{name}}} = {{#isEnum}}{{^isContainer}}{{#enumName}}{{enumName}}.valueOf({{{defaultValue}}}){{/enumName}}{{^enumName}}{{{defaultValue}}}{{/enumName}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/defaultValue}}{{/vendorExtensions.x-self-and-ancestor-only-props}}; + static void _defaults({{{classname}}}Builder b) => b{{#vendorExtensions.x-parent-discriminator}}..{{propertyName}}=b.discriminatorValue{{/vendorExtensions.x-parent-discriminator}}{{#vendorExtensions.x-self-and-ancestor-only-props}}{{#vendorExtensions.x-is-optional}} + ..{{{name}}} = Optional.absent(){{/vendorExtensions.x-is-optional}}{{^vendorExtensions.x-is-optional}}{{#defaultValue}} + ..{{{name}}} = {{#isEnum}}{{^isContainer}}{{#enumName}}{{enumName}}.valueOf({{{defaultValue}}}){{/enumName}}{{^enumName}}{{{defaultValue}}}{{/enumName}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/defaultValue}}{{/vendorExtensions.x-is-optional}}{{/vendorExtensions.x-self-and-ancestor-only-props}}; {{/vendorExtensions.x-is-parent}} @BuiltValueSerializer(custom: true) static Serializer<{{classname}}> get serializer => _${{classname}}Serializer(); \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_serializer.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_serializer.mustache index 4cd02f3506de..ef0a6301bfa3 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_serializer.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/class_serializer.mustache @@ -11,6 +11,21 @@ class _${{classname}}Serializer implements PrimitiveSerializer<{{classname}}> { FullType specifiedType = FullType.unspecified, }) sync* { {{#vendorExtensions.x-self-and-ancestor-only-props}} + {{#vendorExtensions.x-is-optional}} + if (object.{{{name}}}.isPresent) { + yield r'{{baseName}}'; + final optionalValue = object.{{{name}}}.value; + if (optionalValue == null) { + yield null; + } else { + yield serializers.serialize( + optionalValue, + specifiedType: const {{>serialization/built_value/variable_serializer_type}}, + ); + } + } + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{#required}} {{! A required property need to always be part of the serialized output. @@ -32,6 +47,7 @@ class _${{classname}}Serializer implements PrimitiveSerializer<{{classname}}> { ); } {{/required}} + {{/vendorExtensions.x-is-optional}} {{/vendorExtensions.x-self-and-ancestor-only-props}} } diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/deserialize_properties.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/deserialize_properties.mustache index b5a11de35b0f..7402aeab438b 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/deserialize_properties.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/deserialize_properties.mustache @@ -12,6 +12,14 @@ switch (key) { {{#vendorExtensions.x-self-and-ancestor-only-props}} case r'{{baseName}}': + {{#vendorExtensions.x-is-optional}} + final valueDes = serializers.deserialize( + value, + specifiedType: const {{>serialization/built_value/variable_serializer_type}}, + ) as {{{vendorExtensions.x-unwrapped-datatype-nullable}}}; + result.{{{name}}} = Optional.present(valueDes); + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} final valueDes = serializers.deserialize( value, specifiedType: const FullType{{#isNullable}}.nullable{{/isNullable}}{{^isNullable}}{{^required}}.nullable{{/required}}{{/isNullable}}({{#isContainer}}{{baseType}}, [{{#isMap}}FullType(String), {{/isMap}}{{#items}}{{>serialization/built_value/variable_serializer_type}}{{/items}}]{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}), @@ -51,6 +59,7 @@ {{/isModel}} {{/isEnum}} {{/isContainer}} + {{/vendorExtensions.x-is-optional}} break; {{/vendorExtensions.x-self-and-ancestor-only-props}} default: diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_serializer_type.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_serializer_type.mustache index c76dde39a9c9..8116fcb4ede8 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_serializer_type.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_serializer_type.mustache @@ -1 +1 @@ -FullType{{#isNullable}}.nullable{{/isNullable}}({{#isContainer}}{{baseType}}, [{{#isMap}}FullType(String), {{/isMap}}{{#items}}{{>serialization/built_value/variable_serializer_type}}{{/items}}]{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}) \ No newline at end of file +{{#vendorExtensions.x-is-optional}}{{#isContainer}}FullType{{#isNullable}}.nullable{{/isNullable}}({{baseType}}, [{{#isMap}}FullType(String), {{/isMap}}{{#items}}{{>serialization/built_value/variable_serializer_type}}{{/items}}]){{/isContainer}}{{^isContainer}}FullType.nullable({{{vendorExtensions.x-unwrapped-datatype}}}){{/isContainer}}{{/vendorExtensions.x-is-optional}}{{^vendorExtensions.x-is-optional}}FullType{{#isNullable}}.nullable{{/isNullable}}({{#isContainer}}{{baseType}}, [{{#isMap}}FullType(String), {{/isMap}}{{#items}}{{>serialization/built_value/variable_serializer_type}}{{/items}}]{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}){{/vendorExtensions.x-is-optional}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_type.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_type.mustache index 136545525cac..78ca08f2a1c6 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_type.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/variable_type.mustache @@ -1 +1 @@ -{{#isContainer}}{{baseType}}<{{#isMap}}String, {{/isMap}}{{#items}}{{>serialization/built_value/variable_type}}{{/items}}>{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}{{#isNullable}}?{{/isNullable}} \ No newline at end of file +{{#isContainer}}{{baseType}}<{{#isMap}}String, {{/isMap}}{{#items}}{{>serialization/built_value/variable_type}}{{/items}}>{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}{{#isNullable}}{{^vendorExtensions.x-is-optional}}?{{/vendorExtensions.x-is-optional}}{{/isNullable}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache index 76c15358e2cc..3a830ca88390 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache @@ -52,7 +52,7 @@ import com.fasterxml.jackson.databind.ObjectMapper {{#returnType}} @Suppress("UNCHECKED_CAST") {{/returnType}} - {{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}open suspend fun {{operationId}}({{#allParams}}{{{paramName}}}: {{{dataType}}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}}): HttpResponse<{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{#returnProperty}}{{#isNullable}}?{{/isNullable}}{{/returnProperty}}> { + {{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}open suspend fun {{operationId}}({{#allParams}}{{{paramName}}}: {{{dataType}}}{{^required}}?{{/required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{^defaultValue}}{{^required}} = null{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}): HttpResponse<{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{#returnProperty}}{{#isNullable}}?{{/isNullable}}{{/returnProperty}}> { val localVariableAuthNames = listOf({{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}}) diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin5/service.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin5/service.mustache index 73fb1332fda7..52d19653b5bd 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin5/service.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin5/service.mustache @@ -27,7 +27,7 @@ interface {{classname}}Service { {{/externalDocs}} * @see {{classname}}#{{operationId}} */ - {{#reactive}}{{^isArray}}suspend {{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}): {{>returnTypes}} + {{#reactive}}{{^isArray}}suspend {{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isArray}}{{/reactive}}{{/isBodyParam}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{^defaultValue}}{{^required}} = null{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}): {{>returnTypes}} {{/operation}} } {{/operations}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin6/service.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin6/service.mustache index 0c524ab1ec6a..bb510bcd3367 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin6/service.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/javalin6/service.mustache @@ -29,7 +29,7 @@ interface {{classname}}Service { {{/externalDocs}} * @see {{classname}}#{{operationId}} */ - {{#reactive}}{{^isArray}}suspend {{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}ctx: Context): {{>returnTypes}} + {{#reactive}}{{^isArray}}suspend {{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isArray}}{{/reactive}}{{/isBodyParam}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{^defaultValue}}{{^required}} = null{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}ctx: Context): {{>returnTypes}} {{/operation}} } {{/operations}} diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache index e1a76b210fd7..e398eca12c82 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache @@ -1 +1 @@ -{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}} = {{#isPathParam}}Path{{/isPathParam}}{{#isHeaderParam}}Header{{/isHeaderParam}}{{#isFormParam}}{{#isFile}}File{{/isFile}}{{^isFile}}Form{{/isFile}}{{/isFormParam}}{{#isQueryParam}}Query{{/isQueryParam}}{{#isCookieParam}}Cookie{{/isCookieParam}}{{#isBodyParam}}Body{{/isBodyParam}}({{&defaultValue}}{{^defaultValue}}{{#isPathParam}}...{{/isPathParam}}{{^isPathParam}}{{#isFile}}{{#required}}...{{/required}}{{^required}}None{{/required}}{{/isFile}}{{^isFile}}None{{/isFile}}{{/isPathParam}}{{/defaultValue}}, description="{{description}}"{{#isQueryParam}}, alias="{{baseName}}"{{/isQueryParam}}{{#isFormParam}}, alias="{{baseName}}"{{/isFormParam}}{{#isLong}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isLong}}{{#isInteger}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isInteger}}{{#vendorExtensions.x-regex}}, regex=r"{{.}}"{{/vendorExtensions.x-regex}}{{#minLength}}, min_length={{.}}{{/minLength}}{{#maxLength}}, max_length={{.}}{{/maxLength}}{{^isBodyParam}}{{#vendorExtensions.x-py-example}}, examples=[{{{.}}}]{{/vendorExtensions.x-py-example}}{{/isBodyParam}}{{#isBodyParam}}{{#vendorExtensions.x-py-fastapi-example}}, examples=[{{{.}}}]{{/vendorExtensions.x-py-fastapi-example}}{{/isBodyParam}}) \ No newline at end of file +{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}} = {{#isPathParam}}Path{{/isPathParam}}{{#isHeaderParam}}Header{{/isHeaderParam}}{{#isFormParam}}{{#isFile}}File{{/isFile}}{{^isFile}}Form{{/isFile}}{{/isFormParam}}{{#isQueryParam}}Query{{/isQueryParam}}{{#isCookieParam}}Cookie{{/isCookieParam}}{{#isBodyParam}}Body{{/isBodyParam}}({{&defaultValue}}{{^defaultValue}}{{#required}}...{{/required}}{{^required}}None{{/required}}{{/defaultValue}}, description="{{description}}"{{#isQueryParam}}, alias="{{baseName}}"{{/isQueryParam}}{{#isFormParam}}, alias="{{baseName}}"{{/isFormParam}}{{#isLong}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isLong}}{{#isInteger}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isInteger}}{{#vendorExtensions.x-regex}}, regex=r"{{.}}"{{/vendorExtensions.x-regex}}{{#minLength}}, min_length={{.}}{{/minLength}}{{#maxLength}}, max_length={{.}}{{/maxLength}}{{^isBodyParam}}{{#vendorExtensions.x-py-example}}, examples=[{{{.}}}]{{/vendorExtensions.x-py-example}}{{/isBodyParam}}{{#isBodyParam}}{{#vendorExtensions.x-py-fastapi-example}}, examples=[{{{.}}}]{{/vendorExtensions.x-py-fastapi-example}}{{/isBodyParam}}) diff --git a/modules/openapi-generator/src/main/resources/ruby-nextgen/README.mustache b/modules/openapi-generator/src/main/resources/ruby-nextgen/README.mustache index efdaf524082f..d1dc213b3efc 100644 --- a/modules/openapi-generator/src/main/resources/ruby-nextgen/README.mustache +++ b/modules/openapi-generator/src/main/resources/ruby-nextgen/README.mustache @@ -49,6 +49,9 @@ coexist in the same process — there is no global state. ```ruby client = {{moduleName}}::Client.new(base_url: "{{{basePath}}}") do |config| config.timeout = 10 + # Handed to Faraday verbatim. Needed when the server presents a certificate issued by a + # private CA, which the default trust store does not know: + # config.ssl = { ca_file: "/path/to/root.crt" } {{#authMethods}} {{#isApiKey}} config.api_key = "YOUR_API_KEY" diff --git a/modules/openapi-generator/src/main/resources/ruby-nextgen/configuration.mustache b/modules/openapi-generator/src/main/resources/ruby-nextgen/configuration.mustache index bb0242bceb8e..2a1694b2b6b1 100644 --- a/modules/openapi-generator/src/main/resources/ruby-nextgen/configuration.mustache +++ b/modules/openapi-generator/src/main/resources/ruby-nextgen/configuration.mustache @@ -2,13 +2,18 @@ module {{moduleName}} class Configuration - attr_accessor :base_url, :timeout, :logger, :debugging, :query_array_encoding{{#authMethods}}{{#isApiKey}}, :api_key{{/isApiKey}}{{#isBasicBearer}}, :access_token{{/isBasicBearer}}{{#isBasicBasic}}, :username, :password{{/isBasicBasic}}{{/authMethods}} + # `ssl` is handed to Faraday verbatim, e.g. `{ ca_file: "/path/to/root.crt" }` for a server + # whose certificate is issued by a private CA. Such a server is otherwise unreachable — the + # default trust store holds public authorities only — and no middleware can make up for it: + # TLS is settled when the connection is built, before any middleware runs. + attr_accessor :base_url, :timeout, :logger, :debugging, :query_array_encoding, :ssl{{#authMethods}}{{#isApiKey}}, :api_key{{/isApiKey}}{{#isBasicBearer}}, :access_token{{/isBasicBearer}}{{#isBasicBasic}}, :username, :password{{/isBasicBasic}}{{/authMethods}} def initialize(base_url: nil, **options) @base_url = base_url || '{{{basePath}}}' @timeout = 60 @query_array_encoding = :repeat @debugging = false + @ssl = {} @middlewares = [] options.each do |k, v| raise ArgumentError, "unknown configuration option: #{k}" unless respond_to?("#{k}=") diff --git a/modules/openapi-generator/src/main/resources/ruby-nextgen/connection.mustache b/modules/openapi-generator/src/main/resources/ruby-nextgen/connection.mustache index 74c86fbff0c2..37ec2c76b448 100644 --- a/modules/openapi-generator/src/main/resources/ruby-nextgen/connection.mustache +++ b/modules/openapi-generator/src/main/resources/ruby-nextgen/connection.mustache @@ -10,7 +10,9 @@ module {{moduleName}} # relative (their leading slash is stripped in #call) and resolved against it. base = configuration.base_url base += '/' unless base.end_with?('/') - @faraday = Faraday.new(url: base) do |conn| + # `ssl` belongs to the connection options and not to `configure_faraday`: Faraday settles + # TLS when it builds the connection, so a middleware could never supply it. + @faraday = Faraday.new(url: base, ssl: configuration.ssl) do |conn| configuration.configure_faraday(conn) end end diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/jsonSupport.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/jsonSupport.mustache index 0ff02c740c9c..a040bc9726f1 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/jsonSupport.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/jsonSupport.mustache @@ -12,6 +12,9 @@ import com.github.plokhotnyuk.jsoniter_scala.circe.JsoniterScalaCodec.* object JsonSupport extends AdditionalTypeSerializers: inline given CodecMakerConfig = CodecMakerConfig.withAllowRecursiveTypes(true) + given [A](using JsonValueCodec[A]): JsonValueCodec[Option[A]] = deriveJsonCodec + given [A](using JsonValueCodec[A]): JsonValueCodec[Seq[A]] = deriveJsonCodec + inline def deriveJsonCodec[A](using inline config: CodecMakerConfig): JsonValueCodec[A] = JsonCodecMaker.make(config) diff --git a/modules/openapi-generator/src/main/resources/typescript-fetch/validationAttributes.mustache b/modules/openapi-generator/src/main/resources/typescript-fetch/validationAttributes.mustache index 6dddc042bac0..9b338f359c3c 100644 --- a/modules/openapi-generator/src/main/resources/typescript-fetch/validationAttributes.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-fetch/validationAttributes.mustache @@ -2,6 +2,8 @@ export const {{classname}}PropertyValidationAttributesMap: { [property: string]: { + dataType?: string, + required?: boolean, maxLength?: number, minLength?: number, pattern?: string, @@ -18,6 +20,12 @@ export const {{classname}}PropertyValidationAttributesMap: { {{#vars}} {{#hasValidation}} {{name}}: { + {{#dataType}} + dataType: "{{{dataType}}}", + {{/dataType}} + {{#required}} + required: {{required}}, + {{/required}} {{#maxLength}} maxLength: {{maxLength}}, {{/maxLength}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java index 2e5b97cb646f..b8faba728be8 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java @@ -283,6 +283,45 @@ public void testGeneratePingDefaultArrayValue() throws Exception { output.deleteOnExit(); } + @Test + public void testBuilderFieldMatchesJsonNullableFieldType_issue24561() throws Exception { + final File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + Map properties = new HashMap<>(); + properties.put(org.openapitools.codegen.languages.AbstractJavaCodegen.GENERATE_BUILDERS, true); + properties.put(org.openapitools.codegen.languages.AbstractJavaCodegen.OPENAPI_NULLABLE, true); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("jaxrs-spec") + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/bugs/issue_24561.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + List files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + // The pojo field is JsonNullable, so the builder field must be too: the + // generated copy constructor does `this.nullableProperty = b.nullableProperty`, which + // did not compile while the builder declared a plain String. The builder's setter still + // takes the raw type and wraps it, matching the pojo's own fluent setter. + validateJavaSourceFiles(files); + + Path path = Paths.get(output.toPath() + "/src/gen/java/org/openapitools/model/Thing.java"); + String thing = Files.readString(path); + String builder = thing.substring(thing.indexOf("public static abstract class ThingBuilder")); + + // The builder field must be JsonNullable, matching the pojo field it is copied + // into. It previously declared a plain String, so `this.x = b.x` did not compile. + assertTrue(builder.contains("private JsonNullable nullableProperty = JsonNullable.undefined();"), + "builder field for a JsonNullable property must be JsonNullable, but was:\n" + builder); + // The setter still takes the raw type and wraps it, like the pojo's fluent setter. + assertTrue(builder.contains("this.nullableProperty = JsonNullable.of(nullableProperty);"), + "builder setter must wrap with JsonNullable.of(), but was:\n" + builder); + // A property without the extension is untouched. + assertTrue(builder.contains("private String plainProperty;"), + "plain property must keep its raw type in the builder, but was:\n" + builder); + } + @Test public void testGeneratePingNoSpecFile() throws Exception { Map properties = new HashMap<>(); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java index b5466936da39..04d59b0700f4 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java @@ -102,6 +102,23 @@ public void testUseResponseAsReturnType(Object useResponseAsReturnType, String e assertFileContainsLine(lines, "suspend fun deletePet(@Path(\"petId\") petId: kotlin.Long, @Header(\"api_key\") apiKey: kotlin.String? = null)" + expectedUnitResponse); } + @Test + public void testOptionalParamsHaveDefaultNullJvmKtor() throws IOException { + OpenAPI openAPI = readOpenAPI("3_0/kotlin/petstore.yaml"); + + KotlinClientCodegen codegen = createCodegen(ClientLibrary.JVM_KTOR); + + ClientOptInput input = createClientOptInput(openAPI, codegen); + + DefaultGenerator generator = new DefaultGenerator(); + enableOnlyApiGeneration(generator); + + List files = generator.opts(input).generate(); + File petApi = files.stream().filter(file -> file.getName().equals("PetApi.kt")).findAny().orElseThrow(); + + assertFileContains(petApi.toPath(), "apiKey: kotlin.String? = null"); + } + @Test public void testEnumDefaultForReferencedSchemaParameterJvmOkhttp4() throws IOException { OpenAPI openAPI = readOpenAPI("3_0/kotlin/enum-default-query.yaml"); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java index 34d19499cec7..26b9f73e20be 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java @@ -71,7 +71,7 @@ public void testRequestBodyExampleInBodyMetadata() throws IOException { final Path p = Paths.get(outputPath + "src/openapi_server/apis/user_api.py"); assertFileExists(p); - assertFileContains(p, "user: Annotated[List[User], Field(description=\"List of user object\")] = Body(None, description=\"List of user object\", examples=[[{\"username\": \"foo\"}, {\"username\": \"bar\"}]])"); + assertFileContains(p, "user: Annotated[List[User], Field(description=\"List of user object\")] = Body(..., description=\"List of user object\", examples=[[{\"username\": \"foo\"}, {\"username\": \"bar\"}]])"); assertFileNotContains(p, "examples=[[[],"); } @@ -122,7 +122,7 @@ public void testBinaryMultipartFieldUsesUploadFile() throws IOException { assertFileContains(api, "image: Optional[UploadFile] = File(None, description=\"Optional image upload\", alias=\"image\")"); // Sibling non-binary form fields still use Form() - assertFileContains(api, "collection_name: Annotated[StrictStr, Field(description=\"Name of the collection\")] = Form(None, description=\"Name of the collection\", alias=\"collection_name\")"); + assertFileContains(api, "collection_name: Annotated[StrictStr, Field(description=\"Name of the collection\")] = Form(..., description=\"Name of the collection\", alias=\"collection_name\")"); // The legacy client-side bytes union must not appear for the server signature assertFileNotContains(api, "Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]"); 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 250ee4b22521..25afe074b3df 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 @@ -223,6 +223,28 @@ public void testMultipartFileArrayUsesRepeatedFormFields() throws Exception { TestUtils.assertFileNotContains(api, "files.join(COLLECTION_FORMATS.csv)"); } + @Test + public void generatesNullUnionsForOpenApi31NullableContainers() throws Exception { + final File output = Files.createTempDirectory("typescript_axios_nullable_container_types_").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-axios") + .setInputSpec("src/test/resources/3_1/typescript-axios/nullable-container-types.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(clientOptInput).generate(); + files.forEach(File::deleteOnExit); + + Path file = Paths.get(output + "/api.ts"); + + TestUtils.assertFileContains(file, "'validation'?: { [key: string]: string; } | null;"); + TestUtils.assertFileContains(file, "'requirements'?: Array | null;"); + TestUtils.assertFileContains(file, "'settings': MappingItemResourceSettings | null;"); + } + @Test public void generatesTrailingCommasInAsConstEnumObjects() throws Exception { final File output = Files.createTempDirectory("typescript_axios_trailing_commas_").toFile(); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/validations/oas/OpenApiEvaluatorTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/validations/oas/OpenApiEvaluatorTest.java new file mode 100644 index 000000000000..e882fd21e638 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/validations/oas/OpenApiEvaluatorTest.java @@ -0,0 +1,286 @@ +package org.openapitools.codegen.validations.oas; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import org.openapitools.codegen.validation.Invalid; +import org.openapitools.codegen.validation.ValidationResult; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class OpenApiEvaluatorTest { + + private static OpenAPI buildSpecWithEnumDefault(List enumValues, Object defaultValue) { + OpenAPI openAPI = new OpenAPI(); + openAPI.openapi("3.0.1"); + Components components = new Components(); + ObjectSchema obj = new ObjectSchema(); + StringSchema prop = new StringSchema(); + prop.setEnum(enumValues.stream() + .filter(v -> v instanceof String) + .map(v -> (String) v) + .collect(Collectors.toList())); + prop.setDefault(defaultValue); + obj.addProperty("protocol", prop); + components.addSchemas("Config", obj); + openAPI.setComponents(components); + return openAPI; + } + + private static List getDefaultNotInEnumWarnings(ValidationResult result) { + return result.getWarnings().stream() + .filter(i -> i.getMessage().contains("not in enum")) + .collect(Collectors.toList()); + } + + @Test(description = "warn when default is not in enum") + public void testDefaultNotInEnum() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = buildSpecWithEnumDefault(Arrays.asList("udp", "tcp"), "http"); + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 1); + Assert.assertTrue(warnings.get(0).getMessage().contains("'http'")); + Assert.assertTrue(warnings.get(0).getMessage().contains("[udp, tcp]")); + } + + @Test(description = "no warning when default is in enum") + public void testDefaultInEnum() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = buildSpecWithEnumDefault(Arrays.asList("http", "https"), "http"); + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 0); + } + + @Test(description = "no warning when rule is disabled individually") + public void testDefaultNotInEnumDisabledRule() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + config.setEnableDefaultNotInEnumRecommendation(false); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = buildSpecWithEnumDefault(Arrays.asList("udp"), "http"); + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 0); + } + + @Test(description = "no warning when all recommendations are disabled") + public void testDefaultNotInEnumRecommendationsOff() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(false); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = buildSpecWithEnumDefault(Arrays.asList("udp"), "http"); + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 0); + } + + @Test(description = "multiple schemas with default not in enum produce separate warnings") + public void testDefaultNotInEnumMultipleOccurrences() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = new OpenAPI(); + openAPI.openapi("3.0.1"); + Components components = new Components(); + + ObjectSchema udpConfig = new ObjectSchema(); + StringSchema proto1 = new StringSchema(); + proto1.setEnum(Arrays.asList("udp")); + proto1.setDefault("http"); + udpConfig.addProperty("protocol", proto1); + + ObjectSchema tcpConfig = new ObjectSchema(); + StringSchema proto2 = new StringSchema(); + proto2.setEnum(Arrays.asList("tcp")); + proto2.setDefault("http"); + tcpConfig.addProperty("protocol", proto2); + + components.addSchemas("UdpConfig", udpConfig); + components.addSchemas("TcpConfig", tcpConfig); + openAPI.setComponents(components); + + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + // Two property schemas with distinct enum values → two unique messages + Assert.assertEquals(warnings.size(), 2); + } + + @Test(description = "warn for integer default not in integer enum") + public void testDefaultNotInEnumInteger() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = new OpenAPI(); + openAPI.openapi("3.0.1"); + Components components = new Components(); + ObjectSchema obj = new ObjectSchema(); + IntegerSchema prop = new IntegerSchema(); + prop.setEnum(Arrays.asList(1, 2, 3)); + prop.setDefault(99); + obj.addProperty("code", prop); + components.addSchemas("Config", obj); + openAPI.setComponents(components); + + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 1); + Assert.assertTrue(warnings.get(0).getMessage().contains("'99'")); + } + + @Test(description = "no warning when schema has no enum") + public void testNoEnum() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = new OpenAPI(); + openAPI.openapi("3.0.1"); + Components components = new Components(); + ObjectSchema obj = new ObjectSchema(); + StringSchema prop = new StringSchema(); + prop.setDefault("http"); + obj.addProperty("protocol", prop); + components.addSchemas("Config", obj); + openAPI.setComponents(components); + + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 0); + } + + @Test(description = "no warning when schema has no default") + public void testNoDefault() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = new OpenAPI(); + openAPI.openapi("3.0.1"); + Components components = new Components(); + ObjectSchema obj = new ObjectSchema(); + StringSchema prop = new StringSchema(); + prop.setEnum(Arrays.asList("udp", "tcp")); + obj.addProperty("protocol", prop); + components.addSchemas("Config", obj); + openAPI.setComponents(components); + + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 0); + } + + @Test(description = "warn for default not in enum in inline request body schema") + public void testDefaultNotInEnumInlineRequestBody() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = new OpenAPI(); + openAPI.openapi("3.0.1"); + + // Build an inline schema in a request body (not in components/schemas) + ObjectSchema bodySchema = new ObjectSchema(); + StringSchema prop = new StringSchema(); + prop.setEnum(Arrays.asList("udp", "tcp")); + prop.setDefault("http"); + bodySchema.addProperty("protocol", prop); + + MediaType mediaType = new MediaType(); + mediaType.setSchema(bodySchema); + Content content = new Content(); + content.addMediaType("application/json", mediaType); + RequestBody requestBody = new RequestBody(); + requestBody.setContent(content); + + Operation operation = new Operation(); + operation.setRequestBody(requestBody); + operation.setResponses(new ApiResponses()); + + PathItem pathItem = new PathItem(); + pathItem.setPost(operation); + Paths paths = new Paths(); + paths.addPathItem("/test", pathItem); + openAPI.setPaths(paths); + + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 1); + Assert.assertTrue(warnings.get(0).getMessage().contains("'http'")); + } + + @Test(description = "warn for default not in enum in inline response schema") + public void testDefaultNotInEnumInlineResponse() { + RuleConfiguration config = new RuleConfiguration(); + config.setEnableRecommendations(true); + OpenApiEvaluator evaluator = new OpenApiEvaluator(config); + + OpenAPI openAPI = new OpenAPI(); + openAPI.openapi("3.0.1"); + + // Build an inline schema in a response (not in components/schemas) + ObjectSchema responseSchema = new ObjectSchema(); + StringSchema prop = new StringSchema(); + prop.setEnum(Arrays.asList("tcp")); + prop.setDefault("http"); + responseSchema.addProperty("protocol", prop); + + MediaType mediaType = new MediaType(); + mediaType.setSchema(responseSchema); + Content content = new Content(); + content.addMediaType("application/json", mediaType); + ApiResponse apiResponse = new ApiResponse(); + apiResponse.setContent(content); + ApiResponses responses = new ApiResponses(); + responses.addApiResponse("200", apiResponse); + + Operation operation = new Operation(); + operation.setResponses(responses); + + PathItem pathItem = new PathItem(); + pathItem.setGet(operation); + Paths paths = new Paths(); + paths.addPathItem("/test", pathItem); + openAPI.setPaths(paths); + + ValidationResult result = evaluator.validate(openAPI); + + List warnings = getDefaultNotInEnumWarnings(result); + Assert.assertEquals(warnings.size(), 1); + Assert.assertTrue(warnings.get(0).getMessage().contains("'http'")); + } +} diff --git a/modules/openapi-generator/src/test/resources/3_1/typescript-axios/nullable-container-types.yaml b/modules/openapi-generator/src/test/resources/3_1/typescript-axios/nullable-container-types.yaml new file mode 100644 index 000000000000..79c72861de21 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/typescript-axios/nullable-container-types.yaml @@ -0,0 +1,41 @@ +openapi: 3.1.0 +info: + title: Nullable container types + version: 1.0.0 +paths: + /mappings: + get: + operationId: listMappings + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/MappingItemResource' +components: + schemas: + MappingItemResource: + type: object + properties: + settings: + type: + - object + - 'null' + properties: + validation: + type: + - object + - 'null' + additionalProperties: + type: string + requirements: + type: + - array + - 'null' + items: + type: string + required: + - settings diff --git a/modules/openapi-generator/src/test/resources/bugs/issue_24561.yaml b/modules/openapi-generator/src/test/resources/bugs/issue_24561.yaml new file mode 100644 index 000000000000..1389f288af48 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/bugs/issue_24561.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: generateBuilders with a JsonNullable property + version: 1.0.0 +paths: + /thing: + get: + operationId: getThing + responses: + "200": + description: ok + content: + application/json: + schema: + $ref: "#/components/schemas/Thing" +components: + schemas: + Thing: + type: object + properties: + # The pojo field for this property is JsonNullable. The builder + # field has to match, or the generated `this.x = b.x` does not compile. + nullableProperty: + type: string + x-is-jackson-optional-nullable: true + plainProperty: + type: string diff --git a/samples/client/others/ruby-nextgen-qdrant/README.md b/samples/client/others/ruby-nextgen-qdrant/README.md index 69b0fffdfce6..cbb0f80661d8 100644 --- a/samples/client/others/ruby-nextgen-qdrant/README.md +++ b/samples/client/others/ruby-nextgen-qdrant/README.md @@ -49,6 +49,9 @@ coexist in the same process — there is no global state. ```ruby client = Qdrant::Client.new(base_url: "http://localhost:6333") do |config| config.timeout = 10 + # Handed to Faraday verbatim. Needed when the server presents a certificate issued by a + # private CA, which the default trust store does not know: + # config.ssl = { ca_file: "/path/to/root.crt" } config.api_key = "YOUR_API_KEY" config.access_token = "YOUR_ACCESS_TOKEN" end diff --git a/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/configuration.rb b/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/configuration.rb index 3b7fc40d0dba..2649242b82b2 100644 --- a/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/configuration.rb +++ b/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/configuration.rb @@ -2,13 +2,18 @@ module Qdrant class Configuration - attr_accessor :base_url, :timeout, :logger, :debugging, :query_array_encoding, :api_key, :access_token + # `ssl` is handed to Faraday verbatim, e.g. `{ ca_file: "/path/to/root.crt" }` for a server + # whose certificate is issued by a private CA. Such a server is otherwise unreachable — the + # default trust store holds public authorities only — and no middleware can make up for it: + # TLS is settled when the connection is built, before any middleware runs. + attr_accessor :base_url, :timeout, :logger, :debugging, :query_array_encoding, :ssl, :api_key, :access_token def initialize(base_url: nil, **options) @base_url = base_url || 'http://localhost:6333' @timeout = 60 @query_array_encoding = :repeat @debugging = false + @ssl = {} @middlewares = [] options.each do |k, v| raise ArgumentError, "unknown configuration option: #{k}" unless respond_to?("#{k}=") diff --git a/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/connection.rb b/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/connection.rb index 08c3089b9f44..16e028e92939 100644 --- a/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/connection.rb +++ b/samples/client/others/ruby-nextgen-qdrant/lib/qdrant/connection.rb @@ -10,7 +10,9 @@ def initialize(configuration) # relative (their leading slash is stripped in #call) and resolved against it. base = configuration.base_url base += '/' unless base.end_with?('/') - @faraday = Faraday.new(url: base) do |conn| + # `ssl` belongs to the connection options and not to `configure_faraday`: Faraday settles + # TLS when it builds the connection, so a middleware could never supply it. + @faraday = Faraday.new(url: base, ssl: configuration.ssl) do |conn| configuration.configure_faraday(conn) end end diff --git a/samples/client/petstore/java/okhttp-gson-3.1/api/openapi.yaml b/samples/client/petstore/java/okhttp-gson-3.1/api/openapi.yaml index 4348ded2a682..6c8d12078e36 100644 --- a/samples/client/petstore/java/okhttp-gson-3.1/api/openapi.yaml +++ b/samples/client/petstore/java/okhttp-gson-3.1/api/openapi.yaml @@ -325,6 +325,7 @@ paths: additionalProperties: format: int32 type: integer + type: object description: successful operation security: - api_key: [] diff --git a/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/FakeApi.kt b/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/FakeApi.kt index 1e3d3cb24fbe..ad16db242f88 100644 --- a/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/FakeApi.kt +++ b/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/FakeApi.kt @@ -88,7 +88,7 @@ import java.text.DateFormat * @param status2 number type (optional) * @return void */ - open suspend fun updatePetWithFormNumber(petId: kotlin.Long, name: kotlin.String?, status: kotlin.Int?, status2: java.math.BigDecimal?): HttpResponse { + open suspend fun updatePetWithFormNumber(petId: kotlin.Long, name: kotlin.String? = null, status: kotlin.Int? = null, status2: java.math.BigDecimal? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") diff --git a/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 440523a56319..b2801023ac6e 100644 --- a/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-jvm-ktor-gson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -88,7 +88,7 @@ import java.text.DateFormat * @param apiKey (optional) * @return void */ - open suspend fun deletePet(petId: kotlin.Long, apiKey: kotlin.String?): HttpResponse { + open suspend fun deletePet(petId: kotlin.Long, apiKey: kotlin.String? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") @@ -261,7 +261,7 @@ import java.text.DateFormat * @param status Updated status of the pet (optional) * @return void */ - open suspend fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?): HttpResponse { + open suspend fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String? = null, status: kotlin.String? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") @@ -300,7 +300,7 @@ import java.text.DateFormat * @return ModelApiResponse */ @Suppress("UNCHECKED_CAST") - open suspend fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: io.ktor.client.request.forms.FormPart?): HttpResponse { + open suspend fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String? = null, file: io.ktor.client.request.forms.FormPart? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") diff --git a/samples/client/petstore/kotlin-jvm-ktor-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-jvm-ktor-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 902f8f117d63..9c3811f3dca4 100644 --- a/samples/client/petstore/kotlin-jvm-ktor-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-jvm-ktor-jackson/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -85,7 +85,7 @@ import com.fasterxml.jackson.databind.ObjectMapper * @param apiKey (optional) * @return void */ - open suspend fun deletePet(petId: kotlin.Long, apiKey: kotlin.String?): HttpResponse { + open suspend fun deletePet(petId: kotlin.Long, apiKey: kotlin.String? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") @@ -257,7 +257,7 @@ import com.fasterxml.jackson.databind.ObjectMapper * @param status Updated status of the pet (optional) * @return void */ - open suspend fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?): HttpResponse { + open suspend fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String? = null, status: kotlin.String? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") @@ -296,7 +296,7 @@ import com.fasterxml.jackson.databind.ObjectMapper * @return ModelApiResponse */ @Suppress("UNCHECKED_CAST") - open suspend fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: io.ktor.client.request.forms.FormPart?): HttpResponse { + open suspend fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String? = null, file: io.ktor.client.request.forms.FormPart? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") diff --git a/samples/client/petstore/kotlin-jvm-ktor-kotlinx_serialization/src/main/kotlin/org/openapitools/client/apis/PetApi.kt b/samples/client/petstore/kotlin-jvm-ktor-kotlinx_serialization/src/main/kotlin/org/openapitools/client/apis/PetApi.kt index 24e0c4bbab84..5b7ebcca37c2 100644 --- a/samples/client/petstore/kotlin-jvm-ktor-kotlinx_serialization/src/main/kotlin/org/openapitools/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin-jvm-ktor-kotlinx_serialization/src/main/kotlin/org/openapitools/client/apis/PetApi.kt @@ -83,7 +83,7 @@ import io.ktor.http.ParametersBuilder * @param apiKey (optional) * @return void */ - open suspend fun deletePet(petId: kotlin.Long, apiKey: kotlin.String?): HttpResponse { + open suspend fun deletePet(petId: kotlin.Long, apiKey: kotlin.String? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") @@ -256,7 +256,7 @@ import io.ktor.http.ParametersBuilder * @param status Updated status of the pet (optional) * @return void */ - open suspend fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?): HttpResponse { + open suspend fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String? = null, status: kotlin.String? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") @@ -295,7 +295,7 @@ import io.ktor.http.ParametersBuilder * @return ModelApiResponse */ @Suppress("UNCHECKED_CAST") - open suspend fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: io.ktor.client.request.forms.FormPart?): HttpResponse { + open suspend fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String? = null, file: io.ktor.client.request.forms.FormPart? = null): HttpResponse { val localVariableAuthNames = listOf("petstore_auth") diff --git a/samples/client/petstore/ruby-nextgen/README.md b/samples/client/petstore/ruby-nextgen/README.md index 44f5754c89b2..8252bf6e12a9 100644 --- a/samples/client/petstore/ruby-nextgen/README.md +++ b/samples/client/petstore/ruby-nextgen/README.md @@ -49,6 +49,9 @@ coexist in the same process — there is no global state. ```ruby client = Petstore::Client.new(base_url: "http://petstore.swagger.io/v2") do |config| config.timeout = 10 + # Handed to Faraday verbatim. Needed when the server presents a certificate issued by a + # private CA, which the default trust store does not know: + # config.ssl = { ca_file: "/path/to/root.crt" } config.api_key = "YOUR_API_KEY" end ``` diff --git a/samples/client/petstore/ruby-nextgen/lib/petstore/configuration.rb b/samples/client/petstore/ruby-nextgen/lib/petstore/configuration.rb index d83fb7235dd3..d741267c89af 100644 --- a/samples/client/petstore/ruby-nextgen/lib/petstore/configuration.rb +++ b/samples/client/petstore/ruby-nextgen/lib/petstore/configuration.rb @@ -2,13 +2,18 @@ module Petstore class Configuration - attr_accessor :base_url, :timeout, :logger, :debugging, :query_array_encoding, :api_key + # `ssl` is handed to Faraday verbatim, e.g. `{ ca_file: "/path/to/root.crt" }` for a server + # whose certificate is issued by a private CA. Such a server is otherwise unreachable — the + # default trust store holds public authorities only — and no middleware can make up for it: + # TLS is settled when the connection is built, before any middleware runs. + attr_accessor :base_url, :timeout, :logger, :debugging, :query_array_encoding, :ssl, :api_key def initialize(base_url: nil, **options) @base_url = base_url || 'http://petstore.swagger.io/v2' @timeout = 60 @query_array_encoding = :repeat @debugging = false + @ssl = {} @middlewares = [] options.each do |k, v| raise ArgumentError, "unknown configuration option: #{k}" unless respond_to?("#{k}=") diff --git a/samples/client/petstore/ruby-nextgen/lib/petstore/connection.rb b/samples/client/petstore/ruby-nextgen/lib/petstore/connection.rb index 419de06b29f6..457d2b6b2a7a 100644 --- a/samples/client/petstore/ruby-nextgen/lib/petstore/connection.rb +++ b/samples/client/petstore/ruby-nextgen/lib/petstore/connection.rb @@ -10,7 +10,9 @@ def initialize(configuration) # relative (their leading slash is stripped in #call) and resolved against it. base = configuration.base_url base += '/' unless base.end_with?('/') - @faraday = Faraday.new(url: base) do |conn| + # `ssl` belongs to the connection options and not to `configure_faraday`: Faraday settles + # TLS when it builds the connection, so a middleware could never supply it. + @faraday = Faraday.new(url: base, ssl: configuration.ssl) do |conn| configuration.configure_faraday(conn) end end diff --git a/samples/client/petstore/scala-sttp4-jsoniter/src/main/scala/org/openapitools/client/core/JsonSupport.scala b/samples/client/petstore/scala-sttp4-jsoniter/src/main/scala/org/openapitools/client/core/JsonSupport.scala index bb8e506d8a32..57e78fe6fafe 100644 --- a/samples/client/petstore/scala-sttp4-jsoniter/src/main/scala/org/openapitools/client/core/JsonSupport.scala +++ b/samples/client/petstore/scala-sttp4-jsoniter/src/main/scala/org/openapitools/client/core/JsonSupport.scala @@ -20,6 +20,9 @@ import com.github.plokhotnyuk.jsoniter_scala.circe.JsoniterScalaCodec.* object JsonSupport extends AdditionalTypeSerializers: inline given CodecMakerConfig = CodecMakerConfig.withAllowRecursiveTypes(true) + given [A](using JsonValueCodec[A]): JsonValueCodec[Option[A]] = deriveJsonCodec + given [A](using JsonValueCodec[A]): JsonValueCodec[Seq[A]] = deriveJsonCodec + inline def deriveJsonCodec[A](using inline config: CodecMakerConfig): JsonValueCodec[A] = JsonCodecMaker.make(config) diff --git a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Category.ts b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Category.ts index 16ebec06a1d7..7a5fb6a636ea 100644 --- a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Category.ts +++ b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Category.ts @@ -30,6 +30,8 @@ export interface Category { } export const CategoryPropertyValidationAttributesMap: { [property: string]: { + dataType?: string, + required?: boolean, maxLength?: number, minLength?: number, pattern?: string, @@ -44,6 +46,7 @@ export const CategoryPropertyValidationAttributesMap: { } } = { name: { + dataType: "string", pattern: '/^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$/', }, } diff --git a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/ModelApiResponse.ts b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/ModelApiResponse.ts index 90385e16a588..94dbefb8376a 100644 --- a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/ModelApiResponse.ts +++ b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/ModelApiResponse.ts @@ -34,6 +34,8 @@ export interface ModelApiResponse { } export const ModelApiResponsePropertyValidationAttributesMap: { [property: string]: { + dataType?: string, + required?: boolean, maxLength?: number, minLength?: number, pattern?: string, diff --git a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Order.ts b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Order.ts index d3915d0d27c0..0ad9b3725c98 100644 --- a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Order.ts +++ b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Order.ts @@ -59,6 +59,8 @@ export type OrderStatusEnum = typeof OrderStatusEnum[keyof typeof OrderStatusEnu export const OrderPropertyValidationAttributesMap: { [property: string]: { + dataType?: string, + required?: boolean, maxLength?: number, minLength?: number, pattern?: string, diff --git a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Pet.ts b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Pet.ts index eb179370ee05..cde0e7b42de0 100644 --- a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Pet.ts +++ b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Pet.ts @@ -74,6 +74,8 @@ export type PetStatusEnum = typeof PetStatusEnum[keyof typeof PetStatusEnum]; export const PetPropertyValidationAttributesMap: { [property: string]: { + dataType?: string, + required?: boolean, maxLength?: number, minLength?: number, pattern?: string, @@ -88,6 +90,8 @@ export const PetPropertyValidationAttributesMap: { } } = { photoUrls: { + dataType: "Set", + required: true, maxItems: 8, minItems: 1, uniqueItems: true, diff --git a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Tag.ts b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Tag.ts index 82fbdd6fe469..b6222ddd699c 100644 --- a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Tag.ts +++ b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/Tag.ts @@ -30,6 +30,8 @@ export interface Tag { } export const TagPropertyValidationAttributesMap: { [property: string]: { + dataType?: string, + required?: boolean, maxLength?: number, minLength?: number, pattern?: string, diff --git a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/User.ts b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/User.ts index 68761201de1e..af745c1ee60e 100644 --- a/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/User.ts +++ b/samples/client/petstore/typescript-fetch/builds/validation-attributes/models/User.ts @@ -58,6 +58,8 @@ export interface User { } export const UserPropertyValidationAttributesMap: { [property: string]: { + dataType?: string, + required?: boolean, maxLength?: number, minLength?: number, pattern?: string, @@ -72,13 +74,16 @@ export const UserPropertyValidationAttributesMap: { } } = { password: { + dataType: "string", maxLength: 256, minLength: 8, }, nickname: { + dataType: "string", pattern: '/^[a-z&]+$/', }, userStatus: { + dataType: "number", maximum: 100, exclusiveMaximum: true, minimum: 0, diff --git a/samples/server/petstore/kotlin-server-required-and-nullable-properties/src/main/kotlin/org/openapitools/server/apis/DefaultApiService.kt b/samples/server/petstore/kotlin-server-required-and-nullable-properties/src/main/kotlin/org/openapitools/server/apis/DefaultApiService.kt index 7d177214bfe6..9a42651d6001 100644 --- a/samples/server/petstore/kotlin-server-required-and-nullable-properties/src/main/kotlin/org/openapitools/server/apis/DefaultApiService.kt +++ b/samples/server/petstore/kotlin-server-required-and-nullable-properties/src/main/kotlin/org/openapitools/server/apis/DefaultApiService.kt @@ -13,5 +13,5 @@ interface DefaultApiService { * @return Successful operation (status code 200) * @see DefaultApi#addPet */ - fun addPet(pet: Pet?, ctx: Context): Pet + fun addPet(pet: Pet? = null, ctx: Context): Pet } diff --git a/samples/server/petstore/kotlin-server/javalin-6/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt b/samples/server/petstore/kotlin-server/javalin-6/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt index 6c06f129bead..29534749b1ec 100644 --- a/samples/server/petstore/kotlin-server/javalin-6/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt +++ b/samples/server/petstore/kotlin-server/javalin-6/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt @@ -28,7 +28,7 @@ interface PetApiService { * @return Invalid pet value (status code 400) * @see PetApi#deletePet */ - fun deletePet(petId: kotlin.Long, apiKey: kotlin.String?, ctx: Context): Unit + fun deletePet(petId: kotlin.Long, apiKey: kotlin.String? = null, ctx: Context): Unit /** * GET /pet/findByStatus : Finds Pets by status @@ -95,7 +95,7 @@ interface PetApiService { * @return Invalid input (status code 405) * @see PetApi#updatePetWithForm */ - fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?, ctx: Context): Unit + fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String? = null, status: kotlin.String? = null, ctx: Context): Unit /** * POST /pet/{petId}/uploadImage : uploads an image @@ -108,5 +108,5 @@ interface PetApiService { * @return successful operation (status code 200) * @see PetApi#uploadFile */ - fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: io.javalin.http.UploadedFile?, ctx: Context): ModelApiResponse + fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String? = null, file: io.javalin.http.UploadedFile? = null, ctx: Context): ModelApiResponse } diff --git a/samples/server/petstore/kotlin-server/javalin/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt b/samples/server/petstore/kotlin-server/javalin/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt index 1783de4bca80..4a1cdb0a31e2 100644 --- a/samples/server/petstore/kotlin-server/javalin/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt +++ b/samples/server/petstore/kotlin-server/javalin/src/main/kotlin/org/openapitools/server/apis/PetApiService.kt @@ -25,7 +25,7 @@ interface PetApiService { * @return Invalid pet value (status code 400) * @see PetApi#deletePet */ - fun deletePet(petId: kotlin.Long, apiKey: kotlin.String?): Unit + fun deletePet(petId: kotlin.Long, apiKey: kotlin.String? = null): Unit /** * GET /pet/findByStatus : Finds Pets by status @@ -87,7 +87,7 @@ interface PetApiService { * @return Invalid input (status code 405) * @see PetApi#updatePetWithForm */ - fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String?, status: kotlin.String?): Unit + fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String? = null, status: kotlin.String? = null): Unit /** * POST /pet/{petId}/uploadImage : uploads an image @@ -99,5 +99,5 @@ interface PetApiService { * @return successful operation (status code 200) * @see PetApi#uploadFile */ - fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String?, file: io.javalin.http.UploadedFile?): ModelApiResponse + fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String? = null, file: io.javalin.http.UploadedFile? = null): ModelApiResponse } diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py index 0e3f8d51b319..966ef8084147 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py @@ -46,8 +46,10 @@ response_model_by_alias=True, ) async def fake_query_param_default( - has_default: Annotated[Optional[StrictStr], Field(description="has default value")] = Query('Hello World', description="has default value", alias="hasDefault"), - no_default: Annotated[Optional[StrictStr], Field(description="no default value")] = Query(None, description="no default value", alias="noDefault"), + has_default: Annotated[Optional[StrictStr], Field(description="has default value")] = Query('Hello World', description="has default value", alias="hasDefault") +, + no_default: Annotated[Optional[StrictStr], Field(description="no default value")] = Query(None, description="no default value", alias="noDefault") +, ) -> None: """""" if not BaseFakeApi.subclasses: diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py index 7e9fe5def812..7a6d8bdc4ea7 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py @@ -51,7 +51,8 @@ response_model_by_alias=True, ) async def update_pet( - pet: Annotated[Pet, Field(description="Pet object that needs to be added to the store")] = Body(None, description="Pet object that needs to be added to the store"), + pet: Annotated[Pet, Field(description="Pet object that needs to be added to the store")] = Body(..., description="Pet object that needs to be added to the store") +, token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["write:pets", "read:pets"] ), @@ -73,7 +74,8 @@ async def update_pet( response_model_by_alias=True, ) async def add_pet( - pet: Annotated[Pet, Field(description="Pet object that needs to be added to the store")] = Body(None, description="Pet object that needs to be added to the store"), + pet: Annotated[Pet, Field(description="Pet object that needs to be added to the store")] = Body(..., description="Pet object that needs to be added to the store") +, token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["write:pets", "read:pets"] ), @@ -95,7 +97,8 @@ async def add_pet( response_model_by_alias=True, ) async def find_pets_by_status( - status: Annotated[List[StrictStr], Field(description="Status values that need to be considered for filter")] = Query(None, description="Status values that need to be considered for filter", alias="status"), + status: Annotated[List[StrictStr], Field(description="Status values that need to be considered for filter")] = Query(..., description="Status values that need to be considered for filter", alias="status") +, token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["read:pets"] ), @@ -117,7 +120,8 @@ async def find_pets_by_status( response_model_by_alias=True, ) async def find_pets_by_tags( - tags: Annotated[List[StrictStr], Field(description="Tags to filter by")] = Query(None, description="Tags to filter by", alias="tags"), + tags: Annotated[List[StrictStr], Field(description="Tags to filter by")] = Query(..., description="Tags to filter by", alias="tags") +, token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["read:pets"] ), @@ -140,7 +144,8 @@ async def find_pets_by_tags( response_model_by_alias=True, ) async def get_pet_by_id( - petId: Annotated[StrictInt, Field(description="ID of pet to return")] = Path(..., description="ID of pet to return"), + petId: Annotated[StrictInt, Field(description="ID of pet to return")] = Path(..., description="ID of pet to return") +, token_api_key: TokenModel = Security( get_token_api_key ), @@ -161,9 +166,12 @@ async def get_pet_by_id( response_model_by_alias=True, ) async def update_pet_with_form( - petId: Annotated[StrictInt, Field(description="ID of pet that needs to be updated")] = Path(..., description="ID of pet that needs to be updated"), - name: Annotated[Optional[StrictStr], Field(description="Updated name of the pet")] = Form(None, description="Updated name of the pet", alias="name"), - status: Annotated[Optional[StrictStr], Field(description="Updated status of the pet")] = Form(None, description="Updated status of the pet", alias="status"), + petId: Annotated[StrictInt, Field(description="ID of pet that needs to be updated")] = Path(..., description="ID of pet that needs to be updated") +, + name: Annotated[Optional[StrictStr], Field(description="Updated name of the pet")] = Form(None, description="Updated name of the pet", alias="name") +, + status: Annotated[Optional[StrictStr], Field(description="Updated status of the pet")] = Form(None, description="Updated status of the pet", alias="status") +, token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["write:pets", "read:pets"] ), @@ -184,8 +192,10 @@ async def update_pet_with_form( response_model_by_alias=True, ) async def delete_pet( - petId: Annotated[StrictInt, Field(description="Pet id to delete")] = Path(..., description="Pet id to delete"), - api_key: Optional[StrictStr] = Header(None, description=""), + petId: Annotated[StrictInt, Field(description="Pet id to delete")] = Path(..., description="Pet id to delete") +, + api_key: Optional[StrictStr] = Header(None, description="") +, token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["write:pets", "read:pets"] ), @@ -206,9 +216,12 @@ async def delete_pet( response_model_by_alias=True, ) async def upload_file( - petId: Annotated[StrictInt, Field(description="ID of pet to update")] = Path(..., description="ID of pet to update"), - additional_metadata: Annotated[Optional[StrictStr], Field(description="Additional data to pass to server")] = Form(None, description="Additional data to pass to server", alias="additionalMetadata"), - file: Optional[UploadFile] = File(None, description="file to upload", alias="file"), + petId: Annotated[StrictInt, Field(description="ID of pet to update")] = Path(..., description="ID of pet to update") +, + additional_metadata: Annotated[Optional[StrictStr], Field(description="Additional data to pass to server")] = Form(None, description="Additional data to pass to server", alias="additionalMetadata") +, + file: Optional[UploadFile] = File(None, description="file to upload", alias="file") +, token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["write:pets", "read:pets"] ), diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py index 3d2744c2e028..6dbefdcaa1a9 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py @@ -67,7 +67,8 @@ async def get_inventory( response_model_by_alias=True, ) async def place_order( - order: Annotated[Order, Field(description="order placed for purchasing the pet")] = Body(None, description="order placed for purchasing the pet"), + order: Annotated[Order, Field(description="order placed for purchasing the pet")] = Body(..., description="order placed for purchasing the pet") +, ) -> Order: """""" if not BaseStoreApi.subclasses: @@ -87,7 +88,8 @@ async def place_order( response_model_by_alias=True, ) async def get_order_by_id( - orderId: Annotated[int, Field(le=5, strict=True, ge=1, description="ID of pet that needs to be fetched")] = Path(..., description="ID of pet that needs to be fetched", ge=1, le=5), + orderId: Annotated[int, Field(le=5, strict=True, ge=1, description="ID of pet that needs to be fetched")] = Path(..., description="ID of pet that needs to be fetched", ge=1, le=5) +, ) -> Order: """For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions""" if not BaseStoreApi.subclasses: @@ -106,7 +108,8 @@ async def get_order_by_id( response_model_by_alias=True, ) async def delete_order( - orderId: Annotated[StrictStr, Field(description="ID of the order that needs to be deleted")] = Path(..., description="ID of the order that needs to be deleted"), + orderId: Annotated[StrictStr, Field(description="ID of the order that needs to be deleted")] = Path(..., description="ID of the order that needs to be deleted") +, ) -> None: """For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors""" if not BaseStoreApi.subclasses: diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py index e7d5ea8011b5..06634461bfa4 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py @@ -46,7 +46,8 @@ response_model_by_alias=True, ) async def create_user( - user: Annotated[User, Field(description="Created user object")] = Body(None, description="Created user object"), + user: Annotated[User, Field(description="Created user object")] = Body(..., description="Created user object") +, token_api_key: TokenModel = Security( get_token_api_key ), @@ -67,7 +68,8 @@ async def create_user( response_model_by_alias=True, ) async def create_users_with_array_input( - user: Annotated[List[User], Field(description="List of user object")] = Body(None, description="List of user object"), + user: Annotated[List[User], Field(description="List of user object")] = Body(..., description="List of user object") +, token_api_key: TokenModel = Security( get_token_api_key ), @@ -88,7 +90,8 @@ async def create_users_with_array_input( response_model_by_alias=True, ) async def create_users_with_list_input( - user: Annotated[List[User], Field(description="List of user object")] = Body(None, description="List of user object"), + user: Annotated[List[User], Field(description="List of user object")] = Body(..., description="List of user object") +, token_api_key: TokenModel = Security( get_token_api_key ), @@ -110,8 +113,10 @@ async def create_users_with_list_input( response_model_by_alias=True, ) async def login_user( - username: Annotated[str, Field(strict=True, description="The user name for login")] = Query(None, description="The user name for login", alias="username", regex=r"^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$"), - password: Annotated[StrictStr, Field(description="The password for login in clear text")] = Query(None, description="The password for login in clear text", alias="password"), + username: Annotated[str, Field(strict=True, description="The user name for login")] = Query(..., description="The user name for login", alias="username", regex=r"^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$") +, + password: Annotated[StrictStr, Field(description="The password for login in clear text")] = Query(..., description="The password for login in clear text", alias="password") +, ) -> str: """""" if not BaseUserApi.subclasses: @@ -151,7 +156,8 @@ async def logout_user( response_model_by_alias=True, ) async def get_user_by_name( - username: Annotated[StrictStr, Field(description="The name that needs to be fetched. Use user1 for testing.")] = Path(..., description="The name that needs to be fetched. Use user1 for testing."), + username: Annotated[StrictStr, Field(description="The name that needs to be fetched. Use user1 for testing.")] = Path(..., description="The name that needs to be fetched. Use user1 for testing.") +, ) -> User: """""" if not BaseUserApi.subclasses: @@ -170,8 +176,10 @@ async def get_user_by_name( response_model_by_alias=True, ) async def update_user( - username: Annotated[StrictStr, Field(description="name that need to be deleted")] = Path(..., description="name that need to be deleted"), - user: Annotated[User, Field(description="Updated user object")] = Body(None, description="Updated user object"), + username: Annotated[StrictStr, Field(description="name that need to be deleted")] = Path(..., description="name that need to be deleted") +, + user: Annotated[User, Field(description="Updated user object")] = Body(..., description="Updated user object") +, token_api_key: TokenModel = Security( get_token_api_key ), @@ -193,7 +201,8 @@ async def update_user( response_model_by_alias=True, ) async def delete_user( - username: Annotated[StrictStr, Field(description="The name that needs to be deleted")] = Path(..., description="The name that needs to be deleted"), + username: Annotated[StrictStr, Field(description="The name that needs to be deleted")] = Path(..., description="The name that needs to be deleted") +, token_api_key: TokenModel = Security( get_token_api_key ),