diff --git a/.github/workflows/samples-rust-server.yaml b/.github/workflows/samples-rust-server.yaml index cecbef24a7fe..2c6eb21cade5 100644 --- a/.github/workflows/samples-rust-server.yaml +++ b/.github/workflows/samples-rust-server.yaml @@ -60,6 +60,10 @@ jobs: cargo build --bin ${package##*/} --features cli target/debug/${package##*/} --help fi + # Test the validate feature if it exists + if cargo read-manifest | grep -q '"validate"'; then + cargo build --features validate --all-targets + fi cargo fmt cargo test cargo clippy diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java index e512515a0978..aa4a0eac94b8 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java @@ -86,6 +86,9 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon private static final String problemJsonMimeType = "application/problem+json"; private static final String problemXmlMimeType = "application/problem+xml"; + // Track if we have models with conflicting names (Ok/Err) that conflict with serde_valid + private boolean hasConflictingModelNames = false; + public RustServerCodegen() { super(); @@ -941,6 +944,11 @@ private void postProcessOperationWithModels(CodegenOperation op, List if (param.contentType != null && isMimetypeJson(param.contentType)) { param.vendorExtensions.put("x-consumes-json", true); } + + // Add a vendor extension to flag if this can have validate() run on it. + if (!param.isUuid && !param.isPrimitiveType && !param.isEnum && (!param.isContainer || !languageSpecificPrimitives.contains(typeMapping.get(param.baseType)))) { + param.vendorExtensions.put("x-can-validate", true); + } } for (CodegenParameter param : op.formParams) { @@ -1454,8 +1462,20 @@ public String toAllOfName(List names, Schema composedSchema) { public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { super.postProcessModelProperty(model, property); + // Check for reserved field names that conflict with serde_valid macro internals + if ("ok".equalsIgnoreCase(property.name) || "err".equalsIgnoreCase(property.name)) { + model.vendorExtensions.put("x-skip-serde-valid", true); + } + + // Mark properties that reference complex types (models) for nested validation + // Only add nested validation for types that reference generated models (contain "models::") + if (property.dataType != null && property.dataType.contains("models::")) { + property.vendorExtensions.put("x-needs-nested-validation", true); + } + // TODO: We should avoid reverse engineering primitive type status from the data type - if (!languageSpecificPrimitives.contains(stripNullable(property.dataType))) { + String strippedType = stripNullable(property.dataType); + if (!languageSpecificPrimitives.contains(strippedType)) { // If we use a more qualified model name, then only camelize the actual type, not the qualifier. if (property.dataType.contains(":")) { int position = property.dataType.lastIndexOf(":"); @@ -1528,7 +1548,32 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert @Override public ModelsMap postProcessModels(ModelsMap objs) { - return super.postProcessModelsEnum(objs); + ModelsMap result = super.postProcessModelsEnum(objs); + + // Check for model names that conflict with serde_valid macro internals + // Once we find one, set a class-level flag that persists across all model batches + if (!hasConflictingModelNames) { + for (ModelMap modelMap : result.getModels()) { + CodegenModel model = modelMap.getModel(); + if ("Ok".equalsIgnoreCase(model.classname) || "Err".equalsIgnoreCase(model.classname)) { + hasConflictingModelNames = true; + additionalProperties.put("hasConflictingModelNames", true); + break; + } + } + } + + // If there are conflicting names (detected in any batch), skip serde_valid for ALL models + if (hasConflictingModelNames) { + for (ModelMap modelMap : result.getModels()) { + CodegenModel model = modelMap.getModel(); + model.vendorExtensions.put("x-skip-serde-valid", true); + } + // Set the flag for this batch's template context + result.put("hasConflictingModelNames", true); + } + + return result; } private void processParam(CodegenParameter param, CodegenOperation op) { @@ -1613,6 +1658,11 @@ private void processParam(CodegenParameter param, CodegenOperation op) { String exampleString = (example != null) ? "Some(" + example + ")" : "None"; param.vendorExtensions.put("x-example", exampleString); } + + // Add a vendor extension to flag if this can have validate() run on it. + if (!param.isUuid && !param.isPrimitiveType && !param.isEnum && (!param.isContainer || !languageSpecificPrimitives.contains(typeMapping.get(param.baseType)))) { + param.vendorExtensions.put("x-can-validate", true); + } } @Override diff --git a/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache b/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache index 04fda37ab4fc..d796b065ddf0 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache @@ -72,6 +72,7 @@ cli = [ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] mock = ["mockall"] +validate = [{{^apiUsesByteArray}}"regex",{{/apiUsesByteArray}} "serde_valid", "swagger/serdevalid"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] native-tls = { version = "0.2", optional = true } @@ -100,6 +101,8 @@ regex = "1.12" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_valid = { version = "0.16", optional = true } + validator = { version = "0.20", features = ["derive"] } # Crates included if required by the API definition diff --git a/modules/openapi-generator/src/main/resources/rust-server/README.mustache b/modules/openapi-generator/src/main/resources/rust-server/README.mustache index 387eb5a8f124..bee28c9363b6 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/README.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/README.mustache @@ -130,6 +130,9 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` * This defaults to disabled and is required for building the included CLI tool. +* `validate` + * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. + * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/modules/openapi-generator/src/main/resources/rust-server/models.mustache b/modules/openapi-generator/src/main/resources/rust-server/models.mustache index 1ae1ed99e55b..b2e9fb779074 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/models.mustache @@ -1,10 +1,21 @@ #![allow(unused_qualifications)] +{{^hasConflictingModelNames}} +#[cfg(not(feature = "validate"))] +use validator::Validate; +use crate::models; +#[cfg(any(feature = "client", feature = "server"))] +use crate::header; +#[cfg(feature = "validate")] +use serde_valid::Validate; +{{/hasConflictingModelNames}} +{{#hasConflictingModelNames}} use validator::Validate; use crate::models; #[cfg(any(feature = "client", feature = "server"))] use crate::header; +{{/hasConflictingModelNames}} {{! Don't "use" structs here - they can conflict with the names of models, and mean that the code won't compile }} {{#models}} {{#model}} @@ -19,6 +30,7 @@ use crate::header; #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +{{^hasConflictingModelNames}}{{^exts.x-skip-serde-valid}}#[cfg_attr(feature = "validate", derive(Validate))]{{/exts.x-skip-serde-valid}}{{/hasConflictingModelNames}} #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))]{{#xmlName}} #[serde(rename = "{{{.}}}")]{{/xmlName}} pub enum {{{classname}}} { @@ -60,11 +72,14 @@ impl std::str::FromStr for {{{classname}}} { {{^isEnum}} {{#dataType}} #[derive(Debug, Clone, PartialEq, {{#exts.x-partial-ord}}PartialOrd, {{/exts.x-partial-ord}}serde::Serialize, serde::Deserialize)] +{{^hasConflictingModelNames}}{{^exts.x-skip-serde-valid}}#[cfg_attr(feature = "validate", derive(Validate))]{{/exts.x-skip-serde-valid}}{{/hasConflictingModelNames}} #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] {{#xmlName}} #[serde(rename = "{{{.}}}")] {{/xmlName}} -pub struct {{{classname}}}({{{dataType}}}); +pub struct {{{classname}}}( +{{>validate}} {{{dataType}}} +); impl std::convert::From<{{{dataType}}}> for {{{classname}}} { fn from(x: {{{dataType}}}) -> Self { @@ -176,6 +191,7 @@ where {{/exts}} {{! vec}} #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +{{^hasConflictingModelNames}}{{^exts.x-skip-serde-valid}}#[cfg_attr(feature = "validate", derive(Validate))]{{/exts.x-skip-serde-valid}}{{/hasConflictingModelNames}} #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct {{{classname}}}( {{#exts}} @@ -272,7 +288,7 @@ impl std::str::FromStr for {{{classname}}} { {{/arrayModelType}} {{^arrayModelType}} {{! general struct}} -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] {{#xmlName}} #[serde(rename = "{{{.}}}")] @@ -288,7 +304,12 @@ pub struct {{{classname}}} { {{/x-item-xml-name}} {{/exts}} {{#hasValidation}} +{{^hasConflictingModelNames}} + #[cfg_attr(not(feature = "validate"), validate( +{{/hasConflictingModelNames}} +{{#hasConflictingModelNames}} #[validate( +{{/hasConflictingModelNames}} {{#maxLength}} {{#minLength}} length(min = {{minLength}}, max = {{maxLength}}), @@ -336,8 +357,19 @@ pub struct {{{classname}}} { length(min = {{minItems}}), {{/minItems}} {{/maxItems}} +{{^hasConflictingModelNames}} + ))] +{{/hasConflictingModelNames}} +{{#hasConflictingModelNames}} )] +{{/hasConflictingModelNames}} {{/hasValidation}} +{{^hasConflictingModelNames}}{{>validate}}{{/hasConflictingModelNames}} +{{^hasConflictingModelNames}} +{{#exts.x-needs-nested-validation}} + #[cfg_attr(feature = "validate", validate)] +{{/exts.x-needs-nested-validation}} +{{/hasConflictingModelNames}} {{#required}} pub {{{name}}}: {{{dataType}}}, {{/required}} @@ -346,6 +378,11 @@ pub struct {{{classname}}} { #[serde(deserialize_with = "swagger::nullable_format::deserialize_optional_nullable")] #[serde(default = "swagger::nullable_format::default_optional_nullable")] {{/isNullable}} +{{^hasConflictingModelNames}} +{{#exts.x-needs-nested-validation}} + #[cfg_attr(feature = "validate", validate)] +{{/exts.x-needs-nested-validation}} +{{/hasConflictingModelNames}} #[serde(skip_serializing_if="Option::is_none")] pub {{{name}}}: Option<{{{dataType}}}>, {{/required}} @@ -365,6 +402,7 @@ lazy_static::lazy_static! { lazy_static::lazy_static! { static ref RE_{{#lambda.uppercase}}{{{classname}}}_{{{name}}}{{/lambda.uppercase}}: regex::bytes::Regex = regex::bytes::Regex::new(r"{{ pattern }}").unwrap(); } +#[cfg(not(feature = "validate"))] fn validate_byte_{{#lambda.lowercase}}{{{classname}}}_{{{name}}}{{/lambda.lowercase}}( b: &swagger::ByteArray ) -> Result<(), validator::ValidationError> { diff --git a/modules/openapi-generator/src/main/resources/rust-server/server-imports.mustache b/modules/openapi-generator/src/main/resources/rust-server/server-imports.mustache index 15932c468d26..a3d067497b26 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/server-imports.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/server-imports.mustache @@ -4,6 +4,8 @@ use http_body_util::{combinators::BoxBody, Full}; use hyper::{body::{Body, Incoming}, HeaderMap, Request, Response, StatusCode}; use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use log::warn; +#[cfg(feature = "validate")] +use serde_valid::Validate; #[allow(unused_imports)] use std::convert::{TryFrom, TryInto}; use std::{convert::Infallible, error::Error}; diff --git a/modules/openapi-generator/src/main/resources/rust-server/server-make-service.mustache b/modules/openapi-generator/src/main/resources/rust-server/server-make-service.mustache index fd20d96c648e..0df19c9af54f 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/server-make-service.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/server-make-service.mustache @@ -9,6 +9,7 @@ where multipart_form_size_limit: Option, {{/apiUsesMultipartFormData}} marker: PhantomData, + validation: bool } impl MakeService @@ -22,7 +23,8 @@ where {{#apiUsesMultipartFormData}} multipart_form_size_limit: Some(8 * 1024 * 1024), {{/apiUsesMultipartFormData}} - marker: PhantomData + marker: PhantomData, + validation: false } } {{#apiUsesMultipartFormData}} @@ -37,6 +39,12 @@ where self } {{/apiUsesMultipartFormData}} + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -51,6 +59,7 @@ where multipart_form_size_limit: Some(8 * 1024 * 1024), {{/apiUsesMultipartFormData}} marker: PhantomData, + validation: self.validation } } } @@ -65,10 +74,8 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()){{^apiUsesMultipartFormData}};{{/apiUsesMultipartFormData}} -{{#apiUsesMultipartFormData}} - .multipart_form_size_limit(self.multipart_form_size_limit); -{{/apiUsesMultipartFormData}} + let service = Service::new(self.api_impl.clone(), self.validation){{^apiUsesMultipartFormData}};{{/apiUsesMultipartFormData}}{{#apiUsesMultipartFormData}} + .multipart_form_size_limit(self.multipart_form_size_limit);{{/apiUsesMultipartFormData}} future::ok(service) } diff --git a/modules/openapi-generator/src/main/resources/rust-server/server-operation.mustache b/modules/openapi-generator/src/main/resources/rust-server/server-operation.mustache index 38cd9cb05389..956e2a5baef6 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/server-operation.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/server-operation.mustache @@ -187,6 +187,10 @@ .expect("Unable to create Bad Request response for missing query parameter {{{baseName}}}")), }; {{/required}} + {{#exts.x-can-validate}} + #[cfg(not(feature = "validate"))] + run_validation!(param_{{{paramName}}}, "{{{baseName}}}", validation); + {{/exts.x-can-validate}} {{/isArray}} {{#-last}} diff --git a/modules/openapi-generator/src/main/resources/rust-server/server-request-body-basic.mustache b/modules/openapi-generator/src/main/resources/rust-server/server-request-body-basic.mustache index 9a30f3dac9bb..a0a856623de0 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/server-request-body-basic.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/server-request-body-basic.mustache @@ -57,3 +57,7 @@ .expect("Unable to create Bad Request response for missing body parameter {{{baseName}}}")), }; {{/required}} + {{#exts.x-can-validate}} + #[cfg(not(feature = "validate"))] + run_validation!(param_{{{paramName}}}, "{{{baseName}}}", validation); + {{/exts.x-can-validate}} diff --git a/modules/openapi-generator/src/main/resources/rust-server/server-service-footer.mustache b/modules/openapi-generator/src/main/resources/rust-server/server-service-footer.mustache index 5f9a8f23e7c1..74a0d6847805 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/server-service-footer.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/server-service-footer.mustache @@ -6,9 +6,8 @@ Box::pin(run( self.api_impl.clone(), req, -{{#apiUsesMultipartFormData}} - self.multipart_form_size_limit, -{{/apiUsesMultipartFormData}} + self.validation{{#apiUsesMultipartFormData}}, + self.multipart_form_size_limit{{/apiUsesMultipartFormData}} )) } } diff --git a/modules/openapi-generator/src/main/resources/rust-server/server-service-header.mustache b/modules/openapi-generator/src/main/resources/rust-server/server-service-header.mustache index 99a93fcf7b68..983d37ffb5f1 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/server-service-header.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/server-service-header.mustache @@ -6,6 +6,31 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has {{#hasAuthMethods}}+ Has>{{/hasAuthMethods}} + Send + Sync + 'static @@ -15,21 +40,29 @@ pub struct Service where multipart_form_size_limit: Option, {{/apiUsesMultipart}} marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has {{#hasAuthMethods}}+ Has>{{/hasAuthMethods}} + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, {{#apiUsesMultipart}} multipart_form_size_limit: Some(8 * 1024 * 1024), {{/apiUsesMultipart}} - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + {{#apiUsesMultipart}} /// Configure size limit when extracting a multipart/form body. @@ -55,6 +88,7 @@ impl Clone for Service where multipart_form_size_limit: Some(8 * 1024 * 1024), {{/apiUsesMultipart}} marker: self.marker, + validation: self.validation, } } } @@ -83,6 +117,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, {{#apiUsesMultipartFormData}} multipart_form_size_limit: Option, {{/apiUsesMultipartFormData}} diff --git a/modules/openapi-generator/src/main/resources/rust-server/validate.mustache b/modules/openapi-generator/src/main/resources/rust-server/validate.mustache new file mode 100644 index 000000000000..696b6aa7a8cd --- /dev/null +++ b/modules/openapi-generator/src/main/resources/rust-server/validate.mustache @@ -0,0 +1,60 @@ +{{#exts.x-true-type}} +{{#minimum}} + {{#exclusiveMinimum}} + #[cfg_attr(feature = "validate", validate(exclusive_minimum = {{{minimum}}}{{{exts.x-true-type}}}))] + {{/exclusiveMinimum}} + {{^exclusiveMinimum}} + #[cfg_attr(feature = "validate", validate(minimum = {{{minimum}}}{{{exts.x-true-type}}}))] + {{/exclusiveMinimum}} +{{/minimum}} +{{#maximum}} + {{#exclusiveMaximum}} + #[cfg_attr(feature = "validate", validate(exclusive_maximum = {{{maximum}}}{{{exts.x-true-type}}}))] + {{/exclusiveMaximum}} + {{^exclusiveMaximum}} + #[cfg_attr(feature = "validate", validate(maximum = {{{maximum}}}{{{exts.x-true-type}}}))] + {{/exclusiveMaximum}} +{{/maximum}} +{{/exts.x-true-type}} +{{^exts.x-true-type}} +{{#minimum}} + {{#exclusiveMinimum}} + #[cfg_attr(feature = "validate", validate(exclusive_minimum = {{{minimum}}}{{{dataType}}}))] + {{/exclusiveMinimum}} + {{^exclusiveMinimum}} + #[cfg_attr(feature = "validate", validate(minimum = {{{minimum}}}{{{dataType}}}))] + {{/exclusiveMinimum}} +{{/minimum}} +{{#maximum}} + {{#exclusiveMaximum}} + #[cfg_attr(feature = "validate", validate(exclusive_maximum = {{{maximum}}}{{{dataType}}}))] + {{/exclusiveMaximum}} + {{^exclusiveMaximum}} + #[cfg_attr(feature = "validate", validate(maximum = {{{maximum}}}{{{dataType}}}))] + {{/exclusiveMaximum}} +{{/maximum}} +{{/exts.x-true-type}} +{{#minLength}} + #[cfg_attr(feature = "validate", validate(min_length = {{{minLength}}}))] +{{/minLength}} +{{#maxLength}} + #[cfg_attr(feature = "validate", validate(max_length = {{{maxLength}}}))] +{{/maxLength}} +{{#pattern}} +{{^isByteArray}} + #[cfg_attr(feature = "validate", validate(pattern = r"{{{pattern}}}"))] +{{/isByteArray}} +{{/pattern}} +{{#minItems}} + #[cfg_attr(feature = "validate", validate(min_length = {{{minItems}}}))] +{{/minItems}} +{{#maxItems}} + #[cfg_attr(feature = "validate", validate(max_length = {{{maxItems}}}))] +{{/maxItems}} +{{#isEnum}} + {{#allowableValues}} + {{#isString}} + #[cfg_attr(feature = "validate", validate(enumerate({{#enumVars}}{{{value}}}, {{/enumVars}})))] + {{/isString}} + {{/allowableValues}} +{{/isEnum}} diff --git a/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml b/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml index 49a5b9612245..008604449906 100644 --- a/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml +++ b/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml @@ -172,6 +172,19 @@ definitions: value: description: Inner string type: string + unnamed_allof_under_properties: + description: Model for testing allOf references inside properties + properties: + name: + allOf: + - $ref: '#/definitions/unnamed_reference' + unnamed_reference: + type: integer + maximum: 30 + exclusiveMaximum: true + minimum: 5 + exclusiveMinimum: true + format: int32 # Currently broken - see https://github.com/OpenAPITools/openapi-generator/issues/8 # ArrayOfObjects: # description: An array of objects diff --git a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/.openapi-generator/FILES b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/.openapi-generator/FILES index e27ac4b0b61a..c3b1aaf1f342 100644 --- a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/.openapi-generator/FILES @@ -12,6 +12,8 @@ docs/DummyPutRequest.md docs/GetYamlResponse.md docs/ObjectOfObjects.md docs/ObjectOfObjectsInner.md +docs/UnnamedAllofUnderProperties.md +docs/UnnamedReference.md docs/default_api.md examples/ca.pem examples/client/client_auth.rs diff --git a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/README.md b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/README.md index e275b61d22e3..c8982c5b5c2e 100644 --- a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/README.md +++ b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/README.md @@ -149,6 +149,8 @@ Method | HTTP request | Description - [GetYamlResponse](docs/GetYamlResponse.md) - [ObjectOfObjects](docs/ObjectOfObjects.md) - [ObjectOfObjectsInner](docs/ObjectOfObjectsInner.md) + - [UnnamedAllofUnderProperties](docs/UnnamedAllofUnderProperties.md) + - [UnnamedReference](docs/UnnamedReference.md) ## Documentation For Authorization diff --git a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/api/openapi.yaml b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/api/openapi.yaml index 163a5632db4c..151702f8bf8a 100644 --- a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/api/openapi.yaml +++ b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/api/openapi.yaml @@ -183,6 +183,21 @@ components: description: Inner string type: string type: object + unnamed_allof_under_properties: + description: Model for testing allOf references inside properties + properties: + name: + allOf: + - $ref: "#/components/schemas/unnamed_reference" + type: object + type: object + unnamed_reference: + exclusiveMaximum: true + exclusiveMinimum: true + format: int32 + maximum: 30 + minimum: 5 + type: integer dummyPut_request: properties: id: diff --git a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/docs/UnnamedAllofUnderProperties.md b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/docs/UnnamedAllofUnderProperties.md new file mode 100644 index 000000000000..8bc7c1007234 --- /dev/null +++ b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/docs/UnnamedAllofUnderProperties.md @@ -0,0 +1,10 @@ +# UnnamedAllofUnderProperties + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **u32** | | [optional] [default to None] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/docs/UnnamedReference.md b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/docs/UnnamedReference.md new file mode 100644 index 000000000000..5b6830ddcb8d --- /dev/null +++ b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/docs/UnnamedReference.md @@ -0,0 +1,9 @@ +# UnnamedReference + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/src/models.rs b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/src/models.rs index 4d5412ab82aa..8c288de93243 100644 --- a/samples/server/petstore/rust-server-deprecated/output/rust-server-test/src/models.rs +++ b/samples/server/petstore/rust-server-deprecated/output/rust-server-test/src/models.rs @@ -1305,3 +1305,298 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } } + +/// Model for testing allOf references inside properties +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UnnamedAllofUnderProperties { + #[serde(rename = "name")] + #[validate( + range(min = 5, max = 30), + )] + #[serde(skip_serializing_if="Option::is_none")] + pub name: Option, + +} + + +impl UnnamedAllofUnderProperties { + #[allow(clippy::new_without_default)] + pub fn new() -> UnnamedAllofUnderProperties { + UnnamedAllofUnderProperties { + name: None, + } + } +} + +/// Converts the UnnamedAllofUnderProperties value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::string::ToString for UnnamedAllofUnderProperties { + fn to_string(&self) -> String { + let params: Vec> = vec![ + self.name.as_ref().map(|name| { + [ + "name".to_string(), + name.to_string(), + ].join(",") + }), + ]; + + params.into_iter().flatten().collect::>().join(",") + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UnnamedAllofUnderProperties value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for UnnamedAllofUnderProperties { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub name: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => return std::result::Result::Err("Missing value while parsing UnnamedAllofUnderProperties".to_string()) + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "name" => intermediate_rep.name.push(::from_str(val).map_err(|x| x.to_string())?), + _ => return std::result::Result::Err("Unexpected key while parsing UnnamedAllofUnderProperties".to_string()) + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(UnnamedAllofUnderProperties { + name: intermediate_rep.name.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and hyper::header::HeaderValue + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_value: header::IntoHeaderValue) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match hyper::header::HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err( + format!("Invalid header value for UnnamedAllofUnderProperties - value: {hdr_value} is invalid {e}")) + } + } +} + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: hyper::header::HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => std::result::Result::Ok(header::IntoHeaderValue(value)), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{value}' into UnnamedAllofUnderProperties - {err}")) + } + }, + std::result::Result::Err(e) => std::result::Result::Err( + format!("Unable to convert header: {hdr_value:?} to string: {e}")) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom>> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_values: header::IntoHeaderValue>) -> std::result::Result { + let hdr_values : Vec = hdr_values.0.into_iter().map(|hdr_value| { + hdr_value.to_string() + }).collect(); + + match hyper::header::HeaderValue::from_str(&hdr_values.join(", ")) { + std::result::Result::Ok(hdr_value) => std::result::Result::Ok(hdr_value), + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to convert {hdr_values:?} into a header - {e}")) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_values: hyper::header::HeaderValue) -> std::result::Result { + match hdr_values.to_str() { + std::result::Result::Ok(hdr_values) => { + let hdr_values : std::vec::Vec = hdr_values + .split(',') + .filter_map(|hdr_value| match hdr_value.trim() { + "" => std::option::Option::None, + hdr_value => std::option::Option::Some({ + match ::from_str(hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{hdr_value}' into UnnamedAllofUnderProperties - {err}")) + } + }) + }).collect::, String>>()?; + + std::result::Result::Ok(header::IntoHeaderValue(hdr_values)) + }, + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to parse header: {hdr_values:?} as a string - {e}")), + } + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UnnamedReference(i32); + +impl std::convert::From for UnnamedReference { + fn from(x: i32) -> Self { + UnnamedReference(x) + } +} + +impl std::convert::From for i32 { + fn from(x: UnnamedReference) -> Self { + x.0 + } +} + +impl std::ops::Deref for UnnamedReference { + type Target = i32; + fn deref(&self) -> &i32 { + &self.0 + } +} + +impl std::ops::DerefMut for UnnamedReference { + fn deref_mut(&mut self) -> &mut i32 { + &mut self.0 + } +} + +/// Converts the UnnamedReference value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl ::std::string::ToString for UnnamedReference { + fn to_string(&self) -> String { + self.0.to_string() + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UnnamedReference value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl ::std::str::FromStr for UnnamedReference { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + match std::str::FromStr::from_str(s) { + std::result::Result::Ok(r) => std::result::Result::Ok(UnnamedReference(r)), + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to convert {s} to UnnamedReference: {e:?}")), + } + } +} + +// Methods for converting between header::IntoHeaderValue and hyper::header::HeaderValue + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_value: header::IntoHeaderValue) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match hyper::header::HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err( + format!("Invalid header value for UnnamedReference - value: {hdr_value} is invalid {e}")) + } + } +} + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: hyper::header::HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => std::result::Result::Ok(header::IntoHeaderValue(value)), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{value}' into UnnamedReference - {err}")) + } + }, + std::result::Result::Err(e) => std::result::Result::Err( + format!("Unable to convert header: {hdr_value:?} to string: {e}")) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom>> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_values: header::IntoHeaderValue>) -> std::result::Result { + let hdr_values : Vec = hdr_values.0.into_iter().map(|hdr_value| { + hdr_value.to_string() + }).collect(); + + match hyper::header::HeaderValue::from_str(&hdr_values.join(", ")) { + std::result::Result::Ok(hdr_value) => std::result::Result::Ok(hdr_value), + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to convert {hdr_values:?} into a header - {e}")) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_values: hyper::header::HeaderValue) -> std::result::Result { + match hdr_values.to_str() { + std::result::Result::Ok(hdr_values) => { + let hdr_values : std::vec::Vec = hdr_values + .split(',') + .filter_map(|hdr_value| match hdr_value.trim() { + "" => std::option::Option::None, + hdr_value => std::option::Option::Some({ + match ::from_str(hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{hdr_value}' into UnnamedReference - {err}")) + } + }) + }).collect::, String>>()?; + + std::result::Result::Ok(header::IntoHeaderValue(hdr_values)) + }, + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to parse header: {hdr_values:?} as a string - {e}")), + } + } +} diff --git a/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml b/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml index 4092a7bbebcb..f00467da399a 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml @@ -26,6 +26,7 @@ cli = [ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] mock = ["mockall"] +validate = [ "serde_valid", "swagger/serdevalid"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] native-tls = { version = "0.2", optional = true } @@ -52,6 +53,8 @@ regex = "1.12" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_valid = { version = "0.16", optional = true } + validator = { version = "0.20", features = ["derive"] } # Crates included if required by the API definition diff --git a/samples/server/petstore/rust-server/output/multipart-v3/README.md b/samples/server/petstore/rust-server/output/multipart-v3/README.md index e06570ee4b0e..307aa5064e0d 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/README.md +++ b/samples/server/petstore/rust-server/output/multipart-v3/README.md @@ -115,6 +115,9 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` * This defaults to disabled and is required for building the included CLI tool. +* `validate` + * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. + * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/samples/server/petstore/rust-server/output/multipart-v3/src/models.rs b/samples/server/petstore/rust-server/output/multipart-v3/src/models.rs index 609c0faabb50..146e30a60279 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/src/models.rs +++ b/samples/server/petstore/rust-server/output/multipart-v3/src/models.rs @@ -1,23 +1,30 @@ #![allow(unused_qualifications)] - +#[cfg(not(feature = "validate"))] use validator::Validate; use crate::models; #[cfg(any(feature = "client", feature = "server"))] use crate::header; +#[cfg(feature = "validate")] +use serde_valid::Validate; -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MultipartRelatedRequest { #[serde(rename = "object_field")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub object_field: Option, #[serde(rename = "optional_binary_field")] + #[serde(skip_serializing_if="Option::is_none")] pub optional_binary_field: Option, #[serde(rename = "required_binary_field")] + pub required_binary_field: swagger::ByteArray, } @@ -179,13 +186,15 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MultipartRequestObjectField { #[serde(rename = "field_a")] + pub field_a: String, #[serde(rename = "field_b")] + #[serde(skip_serializing_if="Option::is_none")] pub field_b: Option>, @@ -349,14 +358,16 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MultipleIdenticalMimeTypesPostRequest { #[serde(rename = "binary1")] + #[serde(skip_serializing_if="Option::is_none")] pub binary1: Option, #[serde(rename = "binary2")] + #[serde(skip_serializing_if="Option::is_none")] pub binary2: Option, diff --git a/samples/server/petstore/rust-server/output/multipart-v3/src/server/mod.rs b/samples/server/petstore/rust-server/output/multipart-v3/src/server/mod.rs index 88b6300c0225..0401d842dc72 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/multipart-v3/src/server/mod.rs @@ -4,6 +4,8 @@ use http_body_util::{combinators::BoxBody, Full}; use hyper::{body::{Body, Incoming}, HeaderMap, Request, Response, StatusCode}; use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use log::warn; +#[cfg(feature = "validate")] +use serde_valid::Validate; #[allow(unused_imports)] use std::convert::{TryFrom, TryInto}; use std::{convert::Infallible, error::Error}; @@ -58,6 +60,7 @@ where api_impl: T, multipart_form_size_limit: Option, marker: PhantomData, + validation: bool } impl MakeService @@ -69,7 +72,8 @@ where MakeService { api_impl, multipart_form_size_limit: Some(8 * 1024 * 1024), - marker: PhantomData + marker: PhantomData, + validation: false } } @@ -82,6 +86,12 @@ where self.multipart_form_size_limit = multipart_form_size_limit; self } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -94,6 +104,7 @@ where api_impl: self.api_impl.clone(), multipart_form_size_limit: Some(8 * 1024 * 1024), marker: PhantomData, + validation: self.validation } } } @@ -108,7 +119,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()) + let service = Service::new(self.api_impl.clone(), self.validation) .multipart_form_size_limit(self.multipart_form_size_limit); future::ok(service) @@ -123,6 +134,31 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static @@ -130,19 +166,27 @@ pub struct Service where api_impl: T, multipart_form_size_limit: Option, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, multipart_form_size_limit: Some(8 * 1024 * 1024), - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + /// Configure size limit when extracting a multipart/form body. /// @@ -164,6 +208,7 @@ impl Clone for Service where api_impl: self.api_impl.clone(), multipart_form_size_limit: Some(8 * 1024 * 1024), marker: self.marker, + validation: self.validation, } } } @@ -192,6 +237,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, multipart_form_size_limit: Option, ) -> Result>, crate::ServiceError> where @@ -644,7 +690,8 @@ impl hyper::service::Service<(Request, C)> for Service, + validation: bool } impl MakeService @@ -58,9 +61,16 @@ where pub fn new(api_impl: T) -> Self { MakeService { api_impl, - marker: PhantomData + marker: PhantomData, + validation: false } } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -72,6 +82,7 @@ where Self { api_impl: self.api_impl.clone(), marker: PhantomData, + validation: self.validation } } } @@ -86,7 +97,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()); + let service = Service::new(self.api_impl.clone(), self.validation); future::ok(service) } @@ -100,24 +111,57 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static { api_impl: T, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + } impl Clone for Service where @@ -128,6 +172,7 @@ impl Clone for Service where Service { api_impl: self.api_impl.clone(), marker: self.marker, + validation: self.validation, } } } @@ -156,6 +201,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, ) -> Result>, crate::ServiceError> where T: Api + Clone + Send + 'static, @@ -203,6 +249,8 @@ impl hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service, + validation: bool } impl MakeService @@ -71,9 +74,16 @@ where pub fn new(api_impl: T) -> Self { MakeService { api_impl, - marker: PhantomData + marker: PhantomData, + validation: false } } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -85,6 +95,7 @@ where Self { api_impl: self.api_impl.clone(), marker: PhantomData, + validation: self.validation } } } @@ -99,7 +110,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()); + let service = Service::new(self.api_impl.clone(), self.validation); future::ok(service) } @@ -114,24 +125,57 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static { api_impl: T, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + } impl Clone for Service where @@ -142,6 +186,7 @@ impl Clone for Service where Service { api_impl: self.api_impl.clone(), marker: self.marker, + validation: self.validation, } } } @@ -170,6 +215,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, ) -> Result>, crate::ServiceError> where T: Api + Clone + Send + 'static, @@ -298,6 +344,7 @@ impl hyper::service::Service<(Request, C)> for Service); +pub struct AdditionalPropertiesReferencedAnyOfObject( + std::collections::HashMap +); impl std::convert::From> for AdditionalPropertiesReferencedAnyOfObject { fn from(x: std::collections::HashMap) -> Self { @@ -145,8 +147,11 @@ impl AdditionalPropertiesReferencedAnyOfObject { } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct AdditionalPropertiesWithList(std::collections::HashMap>); +pub struct AdditionalPropertiesWithList( + std::collections::HashMap> +); impl std::convert::From>> for AdditionalPropertiesWithList { fn from(x: std::collections::HashMap>) -> Self { @@ -281,16 +286,18 @@ impl AdditionalPropertiesWithList { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct AdditionalPropertiesWithNullable { #[serde(rename = "nullableString")] + #[serde(deserialize_with = "swagger::nullable_format::deserialize_optional_nullable")] #[serde(default = "swagger::nullable_format::default_optional_nullable")] #[serde(skip_serializing_if="Option::is_none")] pub nullable_string: Option>, #[serde(rename = "nullableMap")] + #[serde(skip_serializing_if="Option::is_none")] pub nullable_map: Option>>, @@ -478,6 +485,7 @@ where } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct AnotherXmlArray( #[serde(serialize_with = "wrap_in_snake_another_xml_inner")] @@ -656,9 +664,12 @@ impl AnotherXmlArray { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "snake_another_xml_inner")] -pub struct AnotherXmlInner(String); +pub struct AnotherXmlInner( + String +); impl std::convert::From for AnotherXmlInner { fn from(x: String) -> Self { @@ -786,11 +797,12 @@ impl AnotherXmlInner { } /// An XML object -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "snake_another_xml_object")] pub struct AnotherXmlObject { #[serde(rename = "inner_string")] + #[serde(skip_serializing_if="Option::is_none")] pub inner_string: Option, @@ -967,8 +979,11 @@ impl AnotherXmlObject { } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct AnyOfGet202Response(swagger::AnyOf2); +pub struct AnyOfGet202Response( + swagger::AnyOf2 +); impl std::convert::From> for AnyOfGet202Response { fn from(x: swagger::AnyOf2) -> Self { @@ -1105,8 +1120,11 @@ impl AnyOfGet202Response { /// Test a model containing an anyOf of a hash map #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct AnyOfHashMapObject(swagger::AnyOf2>); +pub struct AnyOfHashMapObject( + swagger::AnyOf2> +); impl std::convert::From>> for AnyOfHashMapObject { fn from(x: swagger::AnyOf2>) -> Self { @@ -1243,8 +1261,11 @@ impl AnyOfHashMapObject { /// Test a model containing an anyOf #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct AnyOfObject(swagger::AnyOf2); +pub struct AnyOfObject( + swagger::AnyOf2 +); impl std::convert::From> for AnyOfObject { fn from(x: swagger::AnyOf2) -> Self { @@ -1385,6 +1406,7 @@ impl AnyOfObject { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] + #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum AnyOfObjectAnyOf { #[serde(rename = "FOO")] @@ -1502,13 +1524,15 @@ impl AnyOfObjectAnyOf { } /// Test containing an anyOf object -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct AnyOfProperty { #[serde(rename = "requiredAnyOf")] + pub required_any_of: models::AnyOfObject, #[serde(rename = "optionalAnyOf")] + #[serde(skip_serializing_if="Option::is_none")] pub optional_any_of: Option, @@ -1677,16 +1701,18 @@ impl AnyOfProperty { } /// An XML object -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "camelDuplicateXmlObject")] pub struct DuplicateXmlObject { #[serde(rename = "inner_string")] + #[serde(skip_serializing_if="Option::is_none")] pub inner_string: Option, #[serde(rename = "inner_array")] #[serde(serialize_with = "wrap_in_camelXmlInner")] + pub inner_array: models::XmlArray, } @@ -1874,6 +1900,7 @@ impl DuplicateXmlObject { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] + #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum EnumWithStarObject { #[serde(rename = "FOO")] @@ -1995,8 +2022,11 @@ impl EnumWithStarObject { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct Err(String); +pub struct Err( + String +); impl std::convert::From for Err { fn from(x: String) -> Self { @@ -2124,8 +2154,11 @@ impl Err { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct Error(String); +pub struct Error( + String +); impl std::convert::From for Error { fn from(x: String) -> Self { @@ -2258,6 +2291,7 @@ impl Error { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] + #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum FormTestRequestEnumField { #[serde(rename = "one_enum")] @@ -2372,8 +2406,11 @@ impl FormTestRequestEnumField { /// Test a model containing an anyOf that starts with a number #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct Model12345AnyOfObject(swagger::AnyOf2); +pub struct Model12345AnyOfObject( + swagger::AnyOf2 +); impl std::convert::From> for Model12345AnyOfObject { fn from(x: swagger::AnyOf2) -> Self { @@ -2514,6 +2551,7 @@ impl Model12345AnyOfObject { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] + #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum Model12345AnyOfObjectAnyOf { #[serde(rename = "FOO")] @@ -2634,10 +2672,11 @@ impl Model12345AnyOfObjectAnyOf { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MultigetGet201Response { #[serde(rename = "foo")] + #[serde(skip_serializing_if="Option::is_none")] pub foo: Option, @@ -2805,8 +2844,11 @@ impl MultigetGet201Response { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct MyId(i32); +pub struct MyId( + i32 +); impl std::convert::From for MyId { fn from(x: i32) -> Self { @@ -2944,6 +2986,7 @@ impl MyId { } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MyIdList( Vec @@ -3122,8 +3165,11 @@ impl MyIdList { /// An object with no type #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct NoTypeObject(serde_json::Value); +pub struct NoTypeObject( + serde_json::Value +); impl std::convert::From for NoTypeObject { fn from(x: serde_json::Value) -> Self { @@ -3261,8 +3307,11 @@ impl NoTypeObject { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct NullableObject(String); +pub struct NullableObject( + String +); impl std::convert::From for NullableObject { fn from(x: String) -> Self { @@ -3389,31 +3438,36 @@ impl NullableObject { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct NullableTest { #[serde(rename = "nullable")] + pub nullable: swagger::Nullable, #[serde(rename = "nullableWithNullDefault")] + #[serde(deserialize_with = "swagger::nullable_format::deserialize_optional_nullable")] #[serde(default = "swagger::nullable_format::default_optional_nullable")] #[serde(skip_serializing_if="Option::is_none")] pub nullable_with_null_default: Option>, #[serde(rename = "nullableWithPresentDefault")] + #[serde(deserialize_with = "swagger::nullable_format::deserialize_optional_nullable")] #[serde(default = "swagger::nullable_format::default_optional_nullable")] #[serde(skip_serializing_if="Option::is_none")] pub nullable_with_present_default: Option>, #[serde(rename = "nullableWithNoDefault")] + #[serde(deserialize_with = "swagger::nullable_format::deserialize_optional_nullable")] #[serde(default = "swagger::nullable_format::default_optional_nullable")] #[serde(skip_serializing_if="Option::is_none")] pub nullable_with_no_default: Option>, #[serde(rename = "nullableArray")] + #[serde(deserialize_with = "swagger::nullable_format::deserialize_optional_nullable")] #[serde(default = "swagger::nullable_format::default_optional_nullable")] #[serde(skip_serializing_if="Option::is_none")] @@ -3423,6 +3477,7 @@ pub struct NullableTest { #[validate( length(min = 1), )] + #[serde(skip_serializing_if="Option::is_none")] pub min_item_test: Option>, @@ -3430,6 +3485,7 @@ pub struct NullableTest { #[validate( length(max = 2), )] + #[serde(skip_serializing_if="Option::is_none")] pub max_item_test: Option>, @@ -3437,6 +3493,7 @@ pub struct NullableTest { #[validate( length(min = 1, max = 3), )] + #[serde(skip_serializing_if="Option::is_none")] pub min_max_item_test: Option>, @@ -3668,13 +3725,15 @@ impl NullableTest { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ObjectHeader { #[serde(rename = "requiredObjectHeader")] + pub required_object_header: bool, #[serde(rename = "optionalObjectHeader")] + #[serde(skip_serializing_if="Option::is_none")] pub optional_object_header: Option, @@ -3848,16 +3907,18 @@ impl ObjectHeader { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ObjectParam { #[serde(rename = "requiredParam")] + pub required_param: bool, #[serde(rename = "optionalParam")] #[validate( range(min = 1u64, max = 10000000000000000000u64), )] + #[serde(skip_serializing_if="Option::is_none")] pub optional_param: Option, @@ -4031,20 +4092,24 @@ impl ObjectParam { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ObjectUntypedProps { #[serde(rename = "required_untyped")] + pub required_untyped: serde_json::Value, #[serde(rename = "required_untyped_nullable")] + pub required_untyped_nullable: swagger::Nullable, #[serde(rename = "not_required_untyped")] + #[serde(skip_serializing_if="Option::is_none")] pub not_required_untyped: Option, #[serde(rename = "not_required_untyped_nullable")] + #[serde(skip_serializing_if="Option::is_none")] pub not_required_untyped_nullable: Option, @@ -4223,10 +4288,11 @@ impl ObjectUntypedProps { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ObjectWithArrayOfObjects { #[serde(rename = "objectArray")] + #[serde(skip_serializing_if="Option::is_none")] pub object_array: Option>, @@ -4393,8 +4459,11 @@ impl ObjectWithArrayOfObjects { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct Ok(String); +pub struct Ok( + String +); impl std::convert::From for Ok { fn from(x: String) -> Self { @@ -4522,8 +4591,11 @@ impl Ok { } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct OneOfGet200Response(swagger::OneOf2>); +pub struct OneOfGet200Response( + swagger::OneOf2> +); impl std::convert::From>> for OneOfGet200Response { fn from(x: swagger::OneOf2>) -> Self { @@ -4659,8 +4731,11 @@ impl OneOfGet200Response { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct OptionalObjectHeader(i32); +pub struct OptionalObjectHeader( + i32 +); impl std::convert::From for OptionalObjectHeader { fn from(x: i32) -> Self { @@ -4798,8 +4873,11 @@ impl OptionalObjectHeader { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct RequiredObjectHeader(bool); +pub struct RequiredObjectHeader( + bool +); impl std::convert::From for RequiredObjectHeader { fn from(x: bool) -> Self { @@ -4937,8 +5015,11 @@ impl RequiredObjectHeader { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct Result(String); +pub struct Result( + String +); impl std::convert::From for Result { fn from(x: String) -> Self { @@ -5071,6 +5152,7 @@ impl Result { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] + #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum StringEnum { #[serde(rename = "FOO")] @@ -5188,8 +5270,11 @@ impl StringEnum { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct StringObject(String); +pub struct StringObject( + String +); impl std::convert::From for StringObject { fn from(x: String) -> Self { @@ -5318,8 +5403,11 @@ impl StringObject { /// Test a model containing a UUID #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct UuidObject(uuid::Uuid); +pub struct UuidObject( + uuid::Uuid +); impl std::convert::From for UuidObject { fn from(x: uuid::Uuid) -> Self { @@ -5473,6 +5561,7 @@ where } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct XmlArray( #[serde(serialize_with = "wrap_in_camelXmlInner")] @@ -5651,9 +5740,12 @@ impl XmlArray { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] + #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "camelXmlInner")] -pub struct XmlInner(String); +pub struct XmlInner( + String +); impl std::convert::From for XmlInner { fn from(x: String) -> Self { @@ -5781,15 +5873,17 @@ impl XmlInner { } /// An XML object -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "camelXmlObject")] pub struct XmlObject { #[serde(rename = "innerString")] + #[serde(skip_serializing_if="Option::is_none")] pub inner_string: Option, #[serde(rename = "other_inner_rename")] + #[serde(skip_serializing_if="Option::is_none")] pub other_inner_rename: Option, diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs index b8866151d91d..c2c3b8a2337c 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs @@ -4,6 +4,8 @@ use http_body_util::{combinators::BoxBody, Full}; use hyper::{body::{Body, Incoming}, HeaderMap, Request, Response, StatusCode}; use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use log::warn; +#[cfg(feature = "validate")] +use serde_valid::Validate; #[allow(unused_imports)] use std::convert::{TryFrom, TryInto}; use std::{convert::Infallible, error::Error}; @@ -154,6 +156,7 @@ where { api_impl: T, marker: PhantomData, + validation: bool } impl MakeService @@ -164,9 +167,16 @@ where pub fn new(api_impl: T) -> Self { MakeService { api_impl, - marker: PhantomData + marker: PhantomData, + validation: false } } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -178,6 +188,7 @@ where Self { api_impl: self.api_impl.clone(), marker: PhantomData, + validation: self.validation } } } @@ -192,7 +203,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()); + let service = Service::new(self.api_impl.clone(), self.validation); future::ok(service) } @@ -206,24 +217,57 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static { api_impl: T, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + } impl Clone for Service where @@ -234,6 +278,7 @@ impl Clone for Service where Service { api_impl: self.api_impl.clone(), marker: self.marker, + validation: self.validation, } } } @@ -262,6 +307,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, ) -> Result>, crate::ServiceError> where T: Api + Clone + Send + 'static, @@ -1022,6 +1068,8 @@ impl hyper::service::Service<(Request, C)> for Service None, }; + #[cfg(not(feature = "validate"))] + run_validation!(param_some_object, "someObject", validation); let param_some_list = query_params.iter().filter(|e| e.0 == "someList").map(|e| e.1.clone()) .next(); let param_some_list = match param_some_list { @@ -1039,6 +1087,8 @@ impl hyper::service::Service<(Request, C)> for Service None, }; + #[cfg(not(feature = "validate"))] + run_validation!(param_some_list, "someList", validation); let result = api_impl.paramget_get( param_uuid, @@ -1536,6 +1586,8 @@ impl hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service, + validation: bool } impl MakeService @@ -166,9 +169,16 @@ where pub fn new(api_impl: T) -> Self { MakeService { api_impl, - marker: PhantomData + marker: PhantomData, + validation: false } } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -180,6 +190,7 @@ where Self { api_impl: self.api_impl.clone(), marker: PhantomData, + validation: self.validation } } } @@ -194,7 +205,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()); + let service = Service::new(self.api_impl.clone(), self.validation); future::ok(service) } @@ -208,24 +219,57 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static { api_impl: T, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + } impl Clone for Service where @@ -236,6 +280,7 @@ impl Clone for Service where Service { api_impl: self.api_impl.clone(), marker: self.marker, + validation: self.validation, } } } @@ -264,6 +309,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, ) -> Result>, crate::ServiceError> where T: Api + Clone + Send + 'static, @@ -1434,6 +1480,7 @@ impl hyper::service::Service<(Request, C)> for Service>, #[serde(rename = "map_of_map_property")] + #[serde(skip_serializing_if="Option::is_none")] pub map_of_map_property: Option>>, @@ -179,13 +183,15 @@ impl AdditionalPropertiesClass { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct Animal { #[serde(rename = "className")] + pub class_name: String, #[serde(rename = "color")] + #[serde(skip_serializing_if="Option::is_none")] pub color: Option, @@ -360,6 +366,7 @@ impl Animal { } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct AnimalFarm( Vec @@ -536,18 +543,21 @@ impl AnimalFarm { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ApiResponse { #[serde(rename = "code")] + #[serde(skip_serializing_if="Option::is_none")] pub code: Option, #[serde(rename = "type")] + #[serde(skip_serializing_if="Option::is_none")] pub r#type: Option, #[serde(rename = "message")] + #[serde(skip_serializing_if="Option::is_none")] pub message: Option, @@ -736,10 +746,11 @@ impl ApiResponse { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ArrayOfArrayOfNumberOnly { #[serde(rename = "ArrayArrayNumber")] + #[serde(skip_serializing_if="Option::is_none")] pub array_array_number: Option>>, @@ -900,10 +911,11 @@ impl ArrayOfArrayOfNumberOnly { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ArrayOfNumberOnly { #[serde(rename = "ArrayNumber")] + #[serde(skip_serializing_if="Option::is_none")] pub array_number: Option>, @@ -1069,22 +1081,30 @@ impl ArrayOfNumberOnly { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ArrayTest { #[serde(rename = "array_of_string")] + #[serde(skip_serializing_if="Option::is_none")] pub array_of_string: Option>, #[serde(rename = "array_array_of_integer")] + #[serde(skip_serializing_if="Option::is_none")] pub array_array_of_integer: Option>>, #[serde(rename = "array_array_of_model")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub array_array_of_model: Option>>, #[serde(rename = "array_of_enum")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub array_of_enum: Option>, @@ -1265,31 +1285,37 @@ impl ArrayTest { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct Capitalization { #[serde(rename = "smallCamel")] + #[serde(skip_serializing_if="Option::is_none")] pub small_camel: Option, #[serde(rename = "CapitalCamel")] + #[serde(skip_serializing_if="Option::is_none")] pub capital_camel: Option, #[serde(rename = "small_Snake")] + #[serde(skip_serializing_if="Option::is_none")] pub small_snake: Option, #[serde(rename = "Capital_Snake")] + #[serde(skip_serializing_if="Option::is_none")] pub capital_snake: Option, #[serde(rename = "SCA_ETH_Flow_Points")] + #[serde(skip_serializing_if="Option::is_none")] pub sca_eth_flow_points: Option, /// Name of the pet #[serde(rename = "ATT_NAME")] + #[serde(skip_serializing_if="Option::is_none")] pub att_name: Option, @@ -1511,17 +1537,20 @@ impl Capitalization { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct Cat { #[serde(rename = "className")] + pub class_name: String, #[serde(rename = "color")] + #[serde(skip_serializing_if="Option::is_none")] pub color: Option, #[serde(rename = "declawed")] + #[serde(skip_serializing_if="Option::is_none")] pub declawed: Option, @@ -1706,15 +1735,17 @@ impl Cat { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "Category")] pub struct Category { #[serde(rename = "id")] + #[serde(skip_serializing_if="Option::is_none")] pub id: Option, #[serde(rename = "name")] + #[serde(skip_serializing_if="Option::is_none")] pub name: Option, @@ -1893,10 +1924,11 @@ impl Category { } /// Model for testing model with \"_class\" property -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ClassModel { #[serde(rename = "_class")] + #[serde(skip_serializing_if="Option::is_none")] pub _class: Option, @@ -2063,10 +2095,11 @@ impl ClassModel { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct Client { #[serde(rename = "client")] + #[serde(skip_serializing_if="Option::is_none")] pub client: Option, @@ -2233,17 +2266,20 @@ impl Client { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct Dog { #[serde(rename = "className")] + pub class_name: String, #[serde(rename = "color")] + #[serde(skip_serializing_if="Option::is_none")] pub color: Option, #[serde(rename = "breed")] + #[serde(skip_serializing_if="Option::is_none")] pub breed: Option, @@ -2428,11 +2464,12 @@ impl Dog { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "$special[model.name]")] pub struct DollarSpecialLeftSquareBracketModelNameRightSquareBracket { #[serde(rename = "$special[property.name]")] + #[serde(skip_serializing_if="Option::is_none")] pub dollar_special_left_square_bracket_property_name_right_square_bracket: Option, @@ -2599,18 +2636,27 @@ impl DollarSpecialLeftSquareBracketModelNameRightSquareBracket { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct EnumArrays { #[serde(rename = "just_symbol")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub just_symbol: Option, #[serde(rename = "array_enum")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub array_enum: Option>, #[serde(rename = "array_array_enum")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub array_array_enum: Option>>, @@ -2788,6 +2834,7 @@ impl EnumArrays { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum EnumArraysArrayArrayEnumInnerInner { #[serde(rename = "Cat")] @@ -2910,6 +2957,7 @@ impl EnumArraysArrayArrayEnumInnerInner { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum EnumArraysArrayEnumInner { #[serde(rename = "fish")] @@ -3032,6 +3080,7 @@ impl EnumArraysArrayEnumInner { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum EnumArraysJustSymbol { #[serde(rename = ">=")] @@ -3154,6 +3203,7 @@ impl EnumArraysJustSymbol { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum EnumClass { #[serde(rename = "_abc")] @@ -3274,25 +3324,39 @@ impl EnumClass { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct EnumTest { #[serde(rename = "enum_string")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub enum_string: Option, #[serde(rename = "enum_string_required")] + + #[cfg_attr(feature = "validate", validate)] pub enum_string_required: models::EnumTestEnumString, #[serde(rename = "enum_integer")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub enum_integer: Option, #[serde(rename = "enum_number")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub enum_number: Option, #[serde(rename = "outerEnum")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub outer_enum: Option, @@ -3484,6 +3548,7 @@ impl EnumTest { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum EnumTestEnumInteger { #[serde(rename = "1")] @@ -3606,6 +3671,7 @@ impl EnumTestEnumInteger { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum EnumTestEnumString { #[serde(rename = "UPPER")] @@ -3732,6 +3798,7 @@ impl EnumTestEnumString { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum FindPetsByStatusStatusParameterInner { #[serde(rename = "available")] @@ -3852,79 +3919,105 @@ impl FindPetsByStatusStatusParameterInner { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct FormatTest { #[serde(rename = "integer")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( range(min = 10u8, max = 100u8), - )] + ))] + #[cfg_attr(feature = "validate", validate(minimum = 10u8))] + #[cfg_attr(feature = "validate", validate(maximum = 100u8))] + #[serde(skip_serializing_if="Option::is_none")] pub integer: Option, #[serde(rename = "int32")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( range(min = 20u32, max = 200u32), - )] + ))] + #[cfg_attr(feature = "validate", validate(minimum = 20u32))] + #[cfg_attr(feature = "validate", validate(maximum = 200u32))] + #[serde(skip_serializing_if="Option::is_none")] pub int32: Option, #[serde(rename = "int64")] + #[serde(skip_serializing_if="Option::is_none")] pub int64: Option, #[serde(rename = "number")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( range(min = 32.1f64, max = 543.2f64), - )] + ))] + #[cfg_attr(feature = "validate", validate(minimum = 32.1f64))] + #[cfg_attr(feature = "validate", validate(maximum = 543.2f64))] + pub number: f64, #[serde(rename = "float")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( range(min = 54.3f32, max = 987.6f32), - )] + ))] + #[cfg_attr(feature = "validate", validate(minimum = 54.3f32))] + #[cfg_attr(feature = "validate", validate(maximum = 987.6f32))] + #[serde(skip_serializing_if="Option::is_none")] pub float: Option, #[serde(rename = "double")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( range(min = 67.8f64, max = 123.4f64), - )] + ))] + #[cfg_attr(feature = "validate", validate(minimum = 67.8f64))] + #[cfg_attr(feature = "validate", validate(maximum = 123.4f64))] + #[serde(skip_serializing_if="Option::is_none")] pub double: Option, #[serde(rename = "string")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( regex(path = *RE_FORMATTEST_STRING), - )] + ))] + #[cfg_attr(feature = "validate", validate(pattern = r"/[a-z]/i"))] + #[serde(skip_serializing_if="Option::is_none")] pub string: Option, #[serde(rename = "byte")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( custom(function = "validate_byte_formattest_byte") - )] + ))] + pub byte: swagger::ByteArray, #[serde(rename = "binary")] + #[serde(skip_serializing_if="Option::is_none")] pub binary: Option, #[serde(rename = "date")] + pub date: chrono::naive::NaiveDate, #[serde(rename = "dateTime")] + #[serde(skip_serializing_if="Option::is_none")] pub date_time: Option>, #[serde(rename = "uuid")] + #[serde(skip_serializing_if="Option::is_none")] pub uuid: Option, #[serde(rename = "password")] - #[validate( + #[cfg_attr(not(feature = "validate"), validate( length(min = 10, max = 64), - )] + ))] + #[cfg_attr(feature = "validate", validate(min_length = 10))] + #[cfg_attr(feature = "validate", validate(max_length = 64))] + pub password: String, } @@ -3935,6 +4028,7 @@ lazy_static::lazy_static! { lazy_static::lazy_static! { static ref RE_FORMATTEST_BYTE: regex::bytes::Regex = regex::bytes::Regex::new(r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$").unwrap(); } +#[cfg(not(feature = "validate"))] fn validate_byte_formattest_byte( b: &swagger::ByteArray ) -> Result<(), validator::ValidationError> { @@ -4201,14 +4295,16 @@ impl FormatTest { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct HasOnlyReadOnly { #[serde(rename = "bar")] + #[serde(skip_serializing_if="Option::is_none")] pub bar: Option, #[serde(rename = "foo")] + #[serde(skip_serializing_if="Option::is_none")] pub foo: Option, @@ -4386,10 +4482,11 @@ impl HasOnlyReadOnly { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct List { #[serde(rename = "123-list")] + #[serde(skip_serializing_if="Option::is_none")] pub param_123_list: Option, @@ -4556,18 +4653,25 @@ impl List { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MapTest { #[serde(rename = "map_map_of_string")] + #[serde(skip_serializing_if="Option::is_none")] pub map_map_of_string: Option>>, #[serde(rename = "map_map_of_enum")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub map_map_of_enum: Option>>, #[serde(rename = "map_of_enum_string")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub map_of_enum_string: Option>, @@ -4744,6 +4848,7 @@ impl MapTest { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum MapTestMapMapOfEnumValueValue { #[serde(rename = "UPPER")] @@ -4860,18 +4965,23 @@ impl MapTestMapMapOfEnumValueValue { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MixedPropertiesAndAdditionalPropertiesClass { #[serde(rename = "uuid")] + #[serde(skip_serializing_if="Option::is_none")] pub uuid: Option, #[serde(rename = "dateTime")] + #[serde(skip_serializing_if="Option::is_none")] pub date_time: Option>, #[serde(rename = "map")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub map: Option>, @@ -5045,15 +5155,17 @@ impl MixedPropertiesAndAdditionalPropertiesClass { } /// Model for testing model name starting with number -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "Name")] pub struct Model200Response { #[serde(rename = "name")] + #[serde(skip_serializing_if="Option::is_none")] pub name: Option, #[serde(rename = "class")] + #[serde(skip_serializing_if="Option::is_none")] pub class: Option, @@ -5232,22 +5344,26 @@ impl Model200Response { } /// Model for testing model name same as property name -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "Name")] pub struct Name { #[serde(rename = "name")] + pub name: i32, #[serde(rename = "snake_case")] + #[serde(skip_serializing_if="Option::is_none")] pub snake_case: Option, #[serde(rename = "property")] + #[serde(skip_serializing_if="Option::is_none")] pub property: Option, #[serde(rename = "123Number")] + #[serde(skip_serializing_if="Option::is_none")] pub param_123_number: Option, @@ -5443,10 +5559,11 @@ impl Name { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct NumberOnly { #[serde(rename = "JustNumber")] + #[serde(skip_serializing_if="Option::is_none")] pub just_number: Option, @@ -5613,10 +5730,13 @@ impl NumberOnly { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ObjectContainingObjectWithOnlyAdditionalProperties { #[serde(rename = "inner")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub inner: Option, @@ -5779,8 +5899,11 @@ impl ObjectContainingObjectWithOnlyAdditionalProperties { } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct ObjectWithOnlyAdditionalProperties(std::collections::HashMap); +pub struct ObjectWithOnlyAdditionalProperties( + std::collections::HashMap +); impl std::convert::From> for ObjectWithOnlyAdditionalProperties { fn from(x: std::collections::HashMap) -> Self { @@ -5915,31 +6038,39 @@ impl ObjectWithOnlyAdditionalProperties { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "Order")] pub struct Order { #[serde(rename = "id")] + #[serde(skip_serializing_if="Option::is_none")] pub id: Option, #[serde(rename = "petId")] + #[serde(skip_serializing_if="Option::is_none")] pub pet_id: Option, #[serde(rename = "quantity")] + #[serde(skip_serializing_if="Option::is_none")] pub quantity: Option, #[serde(rename = "shipDate")] + #[serde(skip_serializing_if="Option::is_none")] pub ship_date: Option>, #[serde(rename = "status")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub status: Option, #[serde(rename = "complete")] + #[serde(skip_serializing_if="Option::is_none")] pub complete: Option, @@ -6158,6 +6289,7 @@ impl Order { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum OrderStatus { #[serde(rename = "placed")] @@ -6279,8 +6411,11 @@ impl OrderStatus { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct OuterBoolean(bool); +pub struct OuterBoolean( + bool +); impl std::convert::From for OuterBoolean { fn from(x: bool) -> Self { @@ -6417,18 +6552,21 @@ impl OuterBoolean { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct OuterComposite { #[serde(rename = "my_number")] + #[serde(skip_serializing_if="Option::is_none")] pub my_number: Option, #[serde(rename = "my_string")] + #[serde(skip_serializing_if="Option::is_none")] pub my_string: Option, #[serde(rename = "my_boolean")] + #[serde(skip_serializing_if="Option::is_none")] pub my_boolean: Option, @@ -6623,6 +6761,7 @@ impl OuterComposite { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum OuterEnum { #[serde(rename = "placed")] @@ -6744,8 +6883,11 @@ impl OuterEnum { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct OuterNumber(f64); +pub struct OuterNumber( + f64 +); impl std::convert::From for OuterNumber { fn from(x: f64) -> Self { @@ -6883,8 +7025,11 @@ impl OuterNumber { } #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct OuterString(String); +pub struct OuterString( + String +); impl std::convert::From for OuterString { fn from(x: String) -> Self { @@ -7011,29 +7156,41 @@ impl OuterString { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "Pet")] pub struct Pet { #[serde(rename = "id")] + #[serde(skip_serializing_if="Option::is_none")] pub id: Option, #[serde(rename = "category")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub category: Option, #[serde(rename = "name")] + pub name: String, #[serde(rename = "photoUrls")] + pub photo_urls: Vec, #[serde(rename = "tags")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub tags: Option>, #[serde(rename = "status")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub status: Option, @@ -7237,6 +7394,7 @@ impl Pet { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum PetStatus { #[serde(rename = "available")] @@ -7357,14 +7515,16 @@ impl PetStatus { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ReadOnlyFirst { #[serde(rename = "bar")] + #[serde(skip_serializing_if="Option::is_none")] pub bar: Option, #[serde(rename = "baz")] + #[serde(skip_serializing_if="Option::is_none")] pub baz: Option, @@ -7543,11 +7703,12 @@ impl ReadOnlyFirst { } /// Model for testing reserved words -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "Return")] pub struct Return { #[serde(rename = "return")] + #[serde(skip_serializing_if="Option::is_none")] pub r#return: Option, @@ -7714,15 +7875,17 @@ impl Return { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "Tag")] pub struct Tag { #[serde(rename = "id")] + #[serde(skip_serializing_if="Option::is_none")] pub id: Option, #[serde(rename = "name")] + #[serde(skip_serializing_if="Option::is_none")] pub name: Option, @@ -7906,6 +8069,7 @@ impl Tag { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum TestEnumParametersEnumHeaderStringArrayParameterInner { #[serde(rename = ">")] @@ -8028,6 +8192,7 @@ impl TestEnumParametersEnumHeaderStringArrayParameterInner { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum TestEnumParametersEnumHeaderStringParameter { #[serde(rename = "_abc")] @@ -8154,6 +8319,7 @@ impl TestEnumParametersEnumHeaderStringParameter { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum TestEnumParametersEnumQueryDoubleParameter { #[serde(rename = "1.1")] @@ -8276,6 +8442,7 @@ impl TestEnumParametersEnumQueryDoubleParameter { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum TestEnumParametersEnumQueryIntegerParameter { #[serde(rename = "1")] @@ -8399,6 +8566,7 @@ impl TestEnumParametersEnumQueryIntegerParameter { #[allow(non_camel_case_types)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] pub enum TestEnumParametersRequestEnumFormString { #[serde(rename = "_abc")] @@ -8519,40 +8687,48 @@ impl TestEnumParametersRequestEnumFormString { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] #[serde(rename = "User")] pub struct User { #[serde(rename = "id")] + #[serde(skip_serializing_if="Option::is_none")] pub id: Option, #[serde(rename = "username")] + #[serde(skip_serializing_if="Option::is_none")] pub username: Option, #[serde(rename = "firstName")] + #[serde(skip_serializing_if="Option::is_none")] pub first_name: Option, #[serde(rename = "lastName")] + #[serde(skip_serializing_if="Option::is_none")] pub last_name: Option, #[serde(rename = "email")] + #[serde(skip_serializing_if="Option::is_none")] pub email: Option, #[serde(rename = "password")] + #[serde(skip_serializing_if="Option::is_none")] pub password: Option, #[serde(rename = "phone")] + #[serde(skip_serializing_if="Option::is_none")] pub phone: Option, /// User Status #[serde(rename = "userStatus")] + #[serde(skip_serializing_if="Option::is_none")] pub user_status: Option, diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs index 8a2cfd51efbd..57e02efadc1f 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs @@ -4,6 +4,8 @@ use http_body_util::{combinators::BoxBody, Full}; use hyper::{body::{Body, Incoming}, HeaderMap, Request, Response, StatusCode}; use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use log::warn; +#[cfg(feature = "validate")] +use serde_valid::Validate; #[allow(unused_imports)] use std::convert::{TryFrom, TryInto}; use std::{convert::Infallible, error::Error}; @@ -167,6 +169,7 @@ where api_impl: T, multipart_form_size_limit: Option, marker: PhantomData, + validation: bool } impl MakeService @@ -178,7 +181,8 @@ where MakeService { api_impl, multipart_form_size_limit: Some(8 * 1024 * 1024), - marker: PhantomData + marker: PhantomData, + validation: false } } @@ -191,6 +195,12 @@ where self.multipart_form_size_limit = multipart_form_size_limit; self } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -203,6 +213,7 @@ where api_impl: self.api_impl.clone(), multipart_form_size_limit: Some(8 * 1024 * 1024), marker: PhantomData, + validation: self.validation } } } @@ -217,7 +228,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()) + let service = Service::new(self.api_impl.clone(), self.validation) .multipart_form_size_limit(self.multipart_form_size_limit); future::ok(service) @@ -232,6 +243,31 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static @@ -239,19 +275,27 @@ pub struct Service where api_impl: T, multipart_form_size_limit: Option, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, multipart_form_size_limit: Some(8 * 1024 * 1024), - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + /// Configure size limit when extracting a multipart/form body. /// @@ -273,6 +317,7 @@ impl Clone for Service where api_impl: self.api_impl.clone(), multipart_form_size_limit: Some(8 * 1024 * 1024), marker: self.marker, + validation: self.validation, } } } @@ -301,6 +346,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, multipart_form_size_limit: Option, ) -> Result>, crate::ServiceError> where @@ -349,6 +395,8 @@ impl hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service None, }; + #[cfg(not(feature = "validate"))] + run_validation!(param_enum_query_string, "enum_query_string", validation); let param_enum_query_integer = query_params.iter().filter(|e| e.0 == "enum_query_integer").map(|e| e.1.clone()) .next(); let param_enum_query_integer = match param_enum_query_integer { @@ -1106,6 +1168,8 @@ impl hyper::service::Service<(Request, C)> for Service None, }; + #[cfg(not(feature = "validate"))] + run_validation!(param_enum_query_integer, "enum_query_integer", validation); let param_enum_query_double = query_params.iter().filter(|e| e.0 == "enum_query_double").map(|e| e.1.clone()) .next(); let param_enum_query_double = match param_enum_query_double { @@ -1123,6 +1187,8 @@ impl hyper::service::Service<(Request, C)> for Service None, }; + #[cfg(not(feature = "validate"))] + run_validation!(param_enum_query_double, "enum_query_double", validation); // Handle body parameters (note that non-required body parameters will ignore garbage // values, rather than causing a 400 response). Produce warning header and logs for @@ -1403,6 +1469,8 @@ impl hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service, + validation: bool } impl MakeService @@ -58,9 +61,16 @@ where pub fn new(api_impl: T) -> Self { MakeService { api_impl, - marker: PhantomData + marker: PhantomData, + validation: false } } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -72,6 +82,7 @@ where Self { api_impl: self.api_impl.clone(), marker: PhantomData, + validation: self.validation } } } @@ -86,7 +97,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()); + let service = Service::new(self.api_impl.clone(), self.validation); future::ok(service) } @@ -100,24 +111,57 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static { api_impl: T, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Has> + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + } impl Clone for Service where @@ -128,6 +172,7 @@ impl Clone for Service where Service { api_impl: self.api_impl.clone(), marker: self.marker, + validation: self.validation, } } } @@ -156,6 +201,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, ) -> Result>, crate::ServiceError> where T: Api + Clone + Send + 'static, @@ -220,6 +266,7 @@ impl hyper::service::Service<(Request, C)> for Service>, #[serde(rename = "RequiredNullableThing")] + pub required_nullable_thing: swagger::Nullable, } @@ -179,8 +183,11 @@ impl std::convert::TryFrom for header::IntoHeaderVal /// An additionalPropertiesObject #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "validate", derive(Validate))] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct AdditionalPropertiesObject(std::collections::HashMap); +pub struct AdditionalPropertiesObject( + std::collections::HashMap +); impl std::convert::From> for AdditionalPropertiesObject { fn from(x: std::collections::HashMap) -> Self { @@ -306,14 +313,16 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct AllOfObject { #[serde(rename = "sampleBaseProperty")] + #[serde(skip_serializing_if="Option::is_none")] pub sample_base_property: Option, #[serde(rename = "sampleProperty")] + #[serde(skip_serializing_if="Option::is_none")] pub sample_property: Option, @@ -482,10 +491,11 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct BaseAllOf { #[serde(rename = "sampleBaseProperty")] + #[serde(skip_serializing_if="Option::is_none")] pub sample_base_property: Option, @@ -643,13 +653,15 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct DummyPutRequest { #[serde(rename = "id")] + pub id: String, #[serde(rename = "password")] + #[serde(skip_serializing_if="Option::is_none")] pub password: Option, @@ -815,11 +827,12 @@ impl std::convert::TryFrom for header::IntoHeaderVal } /// structured response -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct GetYamlResponse { /// Inner string #[serde(rename = "value")] + #[serde(skip_serializing_if="Option::is_none")] pub value: Option, @@ -978,10 +991,13 @@ impl std::convert::TryFrom for header::IntoHeaderVal } /// An object of objects -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ObjectOfObjects { #[serde(rename = "inner")] + + #[cfg_attr(feature = "validate", validate)] + #[cfg_attr(feature = "validate", validate)] #[serde(skip_serializing_if="Option::is_none")] pub inner: Option, @@ -1134,13 +1150,15 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct ObjectOfObjectsInner { #[serde(rename = "required_thing")] + pub required_thing: String, #[serde(rename = "optional_thing")] + #[serde(skip_serializing_if="Option::is_none")] pub optional_thing: Option, @@ -1304,3 +1322,306 @@ impl std::convert::TryFrom for header::IntoHeaderVal } } } + +/// Model for testing allOf references inside properties +#[derive(Debug, Clone, PartialEq, Validate, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UnnamedAllofUnderProperties { + #[serde(rename = "name")] + #[cfg_attr(not(feature = "validate"), validate( + range(min = 5u32, max = 30u32), + ))] + #[cfg_attr(feature = "validate", validate(exclusive_minimum = 5u32))] + #[cfg_attr(feature = "validate", validate(exclusive_maximum = 30u32))] + + #[serde(skip_serializing_if="Option::is_none")] + pub name: Option, + +} + + +impl UnnamedAllofUnderProperties { + #[allow(clippy::new_without_default)] + pub fn new() -> UnnamedAllofUnderProperties { + UnnamedAllofUnderProperties { + name: None, + } + } +} + +/// Converts the UnnamedAllofUnderProperties value to the Query Parameters representation (style=form, explode=false) +/// specified in +/// Should be implemented in a serde serializer +impl std::fmt::Display for UnnamedAllofUnderProperties { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + self.name.as_ref().map(|name| { + [ + "name".to_string(), + name.to_string(), + ].join(",") + }), + ]; + + write!(f, "{}", params.into_iter().flatten().collect::>().join(",")) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UnnamedAllofUnderProperties value +/// as specified in +/// Should be implemented in a serde deserializer +impl std::str::FromStr for UnnamedAllofUnderProperties { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub name: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => return std::result::Result::Err("Missing value while parsing UnnamedAllofUnderProperties".to_string()) + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "name" => intermediate_rep.name.push(::from_str(val).map_err(|x| x.to_string())?), + _ => return std::result::Result::Err("Unexpected key while parsing UnnamedAllofUnderProperties".to_string()) + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(UnnamedAllofUnderProperties { + name: intermediate_rep.name.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and hyper::header::HeaderValue + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_value: header::IntoHeaderValue) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match hyper::header::HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err( + format!("Invalid header value for UnnamedAllofUnderProperties - value: {hdr_value} is invalid {e}")) + } + } +} + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: hyper::header::HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => std::result::Result::Ok(header::IntoHeaderValue(value)), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{value}' into UnnamedAllofUnderProperties - {err}")) + } + }, + std::result::Result::Err(e) => std::result::Result::Err( + format!("Unable to convert header: {hdr_value:?} to string: {e}")) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom>> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_values: header::IntoHeaderValue>) -> std::result::Result { + let hdr_values : Vec = hdr_values.0.into_iter().map(|hdr_value| { + hdr_value.to_string() + }).collect(); + + match hyper::header::HeaderValue::from_str(&hdr_values.join(", ")) { + std::result::Result::Ok(hdr_value) => std::result::Result::Ok(hdr_value), + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to convert {hdr_values:?} into a header - {e}",)) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_values: hyper::header::HeaderValue) -> std::result::Result { + match hdr_values.to_str() { + std::result::Result::Ok(hdr_values) => { + let hdr_values : std::vec::Vec = hdr_values + .split(',') + .filter_map(|hdr_value| match hdr_value.trim() { + "" => std::option::Option::None, + hdr_value => std::option::Option::Some({ + match ::from_str(hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{hdr_value}' into UnnamedAllofUnderProperties - {err}")) + } + }) + }).collect::, String>>()?; + + std::result::Result::Ok(header::IntoHeaderValue(hdr_values)) + }, + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to parse header: {hdr_values:?} as a string - {e}")), + } + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "validate", derive(Validate))] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UnnamedReference( + #[cfg_attr(feature = "validate", validate(exclusive_minimum = 5i32))] + #[cfg_attr(feature = "validate", validate(exclusive_maximum = 30i32))] + i32 +); + +impl std::convert::From for UnnamedReference { + fn from(x: i32) -> Self { + UnnamedReference(x) + } +} + +impl std::convert::From for i32 { + fn from(x: UnnamedReference) -> Self { + x.0 + } +} + +impl std::ops::Deref for UnnamedReference { + type Target = i32; + fn deref(&self) -> &i32 { + &self.0 + } +} + +impl std::ops::DerefMut for UnnamedReference { + fn deref_mut(&mut self) -> &mut i32 { + &mut self.0 + } +} + +/// Converts the UnnamedReference value to the Query Parameters representation (style=form, explode=false) +/// specified in +/// Should be implemented in a serde serializer +impl std::fmt::Display for UnnamedReference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UnnamedReference value +/// as specified in +/// Should be implemented in a serde deserializer +impl ::std::str::FromStr for UnnamedReference { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + match std::str::FromStr::from_str(s) { + std::result::Result::Ok(r) => std::result::Result::Ok(UnnamedReference(r)), + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to convert {s} to UnnamedReference: {e:?}")), + } + } +} + +// Methods for converting between header::IntoHeaderValue and hyper::header::HeaderValue + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_value: header::IntoHeaderValue) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match hyper::header::HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err( + format!("Invalid header value for UnnamedReference - value: {hdr_value} is invalid {e}")) + } + } +} + +#[cfg(any(feature = "client", feature = "server"))] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: hyper::header::HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => std::result::Result::Ok(header::IntoHeaderValue(value)), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{value}' into UnnamedReference - {err}")) + } + }, + std::result::Result::Err(e) => std::result::Result::Err( + format!("Unable to convert header: {hdr_value:?} to string: {e}")) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom>> for hyper::header::HeaderValue { + type Error = String; + + fn try_from(hdr_values: header::IntoHeaderValue>) -> std::result::Result { + let hdr_values : Vec = hdr_values.0.into_iter().map(|hdr_value| { + hdr_value.to_string() + }).collect(); + + match hyper::header::HeaderValue::from_str(&hdr_values.join(", ")) { + std::result::Result::Ok(hdr_value) => std::result::Result::Ok(hdr_value), + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to convert {hdr_values:?} into a header - {e}",)) + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_values: hyper::header::HeaderValue) -> std::result::Result { + match hdr_values.to_str() { + std::result::Result::Ok(hdr_values) => { + let hdr_values : std::vec::Vec = hdr_values + .split(',') + .filter_map(|hdr_value| match hdr_value.trim() { + "" => std::option::Option::None, + hdr_value => std::option::Option::Some({ + match ::from_str(hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{hdr_value}' into UnnamedReference - {err}")) + } + }) + }).collect::, String>>()?; + + std::result::Result::Ok(header::IntoHeaderValue(hdr_values)) + }, + std::result::Result::Err(e) => std::result::Result::Err(format!("Unable to parse header: {hdr_values:?} as a string - {e}")), + } + } +} diff --git a/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs b/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs index eb1af9c6f09e..29c4795d608e 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs @@ -4,6 +4,8 @@ use http_body_util::{combinators::BoxBody, Full}; use hyper::{body::{Body, Incoming}, HeaderMap, Request, Response, StatusCode}; use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use log::warn; +#[cfg(feature = "validate")] +use serde_valid::Validate; #[allow(unused_imports)] use std::convert::{TryFrom, TryInto}; use std::{convert::Infallible, error::Error}; @@ -70,6 +72,7 @@ where { api_impl: T, marker: PhantomData, + validation: bool } impl MakeService @@ -80,9 +83,16 @@ where pub fn new(api_impl: T) -> Self { MakeService { api_impl, - marker: PhantomData + marker: PhantomData, + validation: false } } + + // Turn on/off validation for the service being made. + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation; + } } impl Clone for MakeService @@ -94,6 +104,7 @@ where Self { api_impl: self.api_impl.clone(), marker: PhantomData, + validation: self.validation } } } @@ -108,7 +119,7 @@ where type Future = future::Ready>; fn call(&self, target: Target) -> Self::Future { - let service = Service::new(self.api_impl.clone()); + let service = Service::new(self.api_impl.clone(), self.validation); future::ok(service) } @@ -122,24 +133,57 @@ fn method_not_allowed() -> Result>, crate::S ) } +#[allow(unused_macros)] +#[cfg(not(feature = "validate"))] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => (); +} + +#[allow(unused_macros)] +#[cfg(feature = "validate")] +macro_rules! run_validation { + ($parameter:tt, $base_name:tt, $validation:tt) => { + let $parameter = if $validation { + match $parameter.validate() { + Ok(()) => $parameter, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(BoxBody::new(format!("Invalid value in body parameter {}: {}", $base_name, e))) + .expect(&format!("Unable to create Bad Request response for invalid value in body parameter {}", $base_name))), + } + } else { + $parameter + }; + } +} + pub struct Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static { api_impl: T, marker: PhantomData, + // Enable regex pattern validation of received JSON models + validation: bool, } impl Service where T: Api + Clone + Send + 'static, C: Has + Send + Sync + 'static { - pub fn new(api_impl: T) -> Self { + pub fn new(api_impl: T, validation: bool) -> Self { Service { api_impl, - marker: PhantomData + marker: PhantomData, + validation, } } + #[cfg(feature = "validate")] + pub fn set_validation(&mut self, validation: bool) { + self.validation = validation + } + } impl Clone for Service where @@ -150,6 +194,7 @@ impl Clone for Service where Service { api_impl: self.api_impl.clone(), marker: self.marker, + validation: self.validation, } } } @@ -178,6 +223,7 @@ impl hyper::service::Service<(Request, C)> for Service( mut api_impl: T, req: (Request, C), + validation: bool, ) -> Result>, crate::ServiceError> where T: Api + Clone + Send + 'static, @@ -292,6 +338,8 @@ impl hyper::service::Service<(Request, C)> for Service hyper::service::Service<(Request, C)> for Service