mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-05-12 12:40:53 +00:00
[Rust-Axum][Breaking Change] Improve the oneOf
model generator (#20336)
* Improve the implementation of oneOf * Fixed 2.0 schemas; possible freeze present * Fix generate-samples.sh freezing * Fix validation of primitive types * Move oneOf handling to its own method * Fix formatting and add comments * Remove allOf based discriminator handling * Implement a test for v3 oneOf * Implement oneOf tests for rust axum * Fix circle CI * Fix pom path, ensure cargo is present * Implement untagged test * Add final and fix double underscore typo
This commit is contained in:
parent
eb96380928
commit
3d65786117
@ -15,12 +15,22 @@ if [ "$NODE_INDEX" = "1" ]; then
|
|||||||
|
|
||||||
sudo apt-get -y install cpanminus
|
sudo apt-get -y install cpanminus
|
||||||
|
|
||||||
|
# install rust
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
source "$HOME/.cargo/env"
|
||||||
|
|
||||||
|
echo "Testing perl"
|
||||||
(cd samples/client/petstore/perl && /bin/bash ./test.bash)
|
(cd samples/client/petstore/perl && /bin/bash ./test.bash)
|
||||||
|
|
||||||
|
echo "Testing ruby"
|
||||||
(cd samples/client/petstore/ruby && mvn integration-test)
|
(cd samples/client/petstore/ruby && mvn integration-test)
|
||||||
(cd samples/client/petstore/ruby-faraday && mvn integration-test)
|
(cd samples/client/petstore/ruby-faraday && mvn integration-test)
|
||||||
(cd samples/client/petstore/ruby-httpx && mvn integration-test)
|
(cd samples/client/petstore/ruby-httpx && mvn integration-test)
|
||||||
(cd samples/client/petstore/ruby-autoload && mvn integration-test)
|
(cd samples/client/petstore/ruby-autoload && mvn integration-test)
|
||||||
|
|
||||||
|
echo "Testing rust"
|
||||||
|
(cd samples/server/petstore/rust-axum && mvn integration-test)
|
||||||
|
|
||||||
elif [ "$NODE_INDEX" = "2" ]; then
|
elif [ "$NODE_INDEX" = "2" ]; then
|
||||||
echo "Running node $NODE_INDEX to test Go"
|
echo "Running node $NODE_INDEX to test Go"
|
||||||
# install haskell
|
# install haskell
|
||||||
|
11
bin/configs/manual/rust-axum-oneof-v3.yaml
Normal file
11
bin/configs/manual/rust-axum-oneof-v3.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
generatorName: rust-axum
|
||||||
|
outputDir: samples/server/petstore/rust-axum/output/rust-axum-oneof
|
||||||
|
inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-oneof.yaml
|
||||||
|
templateDir: modules/openapi-generator/src/main/resources/rust-axum
|
||||||
|
generateAliasAsModel: true
|
||||||
|
additionalProperties:
|
||||||
|
hideGenerationTimestamp: "true"
|
||||||
|
packageName: rust-axum-oneof
|
||||||
|
globalProperties:
|
||||||
|
skipFormModel: "false"
|
||||||
|
enablePostProcessFile: true
|
@ -49,3 +49,8 @@
|
|||||||
sha256: 67a9e63e13ebddac21cb236aa015edce30f5d3bd8d6adcf50044cad00d48c45e
|
sha256: 67a9e63e13ebddac21cb236aa015edce30f5d3bd8d6adcf50044cad00d48c45e
|
||||||
- filename: "samples/openapi3/client/petstore/java/jersey2-java8/src/test/java/org/openapitools/client/model/ZebraTest.java"
|
- filename: "samples/openapi3/client/petstore/java/jersey2-java8/src/test/java/org/openapitools/client/model/ZebraTest.java"
|
||||||
sha256: 15eeb6d8a9a79d0f1930b861540d9c5780d6c49ea4fdb68269ac3e7ec481e142
|
sha256: 15eeb6d8a9a79d0f1930b861540d9c5780d6c49ea4fdb68269ac3e7ec481e142
|
||||||
|
# rust axum test files
|
||||||
|
- filename: "samples/server/petstore/rust-axum/output/rust-axum-oneof/src/tests.rs"
|
||||||
|
sha256: 3d4198174018cc7fd9d4bcffd950609a5bd306cf03b2fa780516f4e22a566e8c
|
||||||
|
- filename: "samples/server/petstore/rust-axum/output/openapi-v3/src/tests.rs"
|
||||||
|
sha256: 356ac684b1fce91b153c63caefc1fe7472ea600ac436a19631e16bc00e986c50
|
||||||
|
@ -1083,8 +1083,8 @@ public class CodegenModel implements IJsonSchemaValidationProperties {
|
|||||||
sb.append(", items='").append(items).append('\'');
|
sb.append(", items='").append(items).append('\'');
|
||||||
sb.append(", additionalProperties='").append(additionalProperties).append('\'');
|
sb.append(", additionalProperties='").append(additionalProperties).append('\'');
|
||||||
sb.append(", isModel='").append(isModel).append('\'');
|
sb.append(", isModel='").append(isModel).append('\'');
|
||||||
sb.append(", isNull='").append(isNull);
|
sb.append(", isNull='").append(isNull).append('\'');
|
||||||
sb.append(", hasValidation='").append(hasValidation);
|
sb.append(", hasValidation='").append(hasValidation).append('\'');
|
||||||
sb.append(", getAdditionalPropertiesIsAnyType=").append(getAdditionalPropertiesIsAnyType());
|
sb.append(", getAdditionalPropertiesIsAnyType=").append(getAdditionalPropertiesIsAnyType());
|
||||||
sb.append(", getHasDiscriminatorWithNonEmptyMapping=").append(hasDiscriminatorWithNonEmptyMapping);
|
sb.append(", getHasDiscriminatorWithNonEmptyMapping=").append(hasDiscriminatorWithNonEmptyMapping);
|
||||||
sb.append(", getIsAnyType=").append(getIsAnyType());
|
sb.append(", getIsAnyType=").append(getIsAnyType());
|
||||||
|
@ -236,8 +236,9 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege
|
|||||||
supportingFiles.add(new SupportingFile("header.mustache", "src", "header.rs"));
|
supportingFiles.add(new SupportingFile("header.mustache", "src", "header.rs"));
|
||||||
supportingFiles.add(new SupportingFile("server-mod.mustache", "src/server", "mod.rs"));
|
supportingFiles.add(new SupportingFile("server-mod.mustache", "src/server", "mod.rs"));
|
||||||
supportingFiles.add(new SupportingFile("apis-mod.mustache", apiPackage().replace('.', File.separatorChar), "mod.rs"));
|
supportingFiles.add(new SupportingFile("apis-mod.mustache", apiPackage().replace('.', File.separatorChar), "mod.rs"));
|
||||||
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
|
// The file gets overwritten regardless
|
||||||
.doNotOverwrite());
|
supportingFiles.add(new SupportingFile("tests.mustache", "src", "tests.rs").doNotOverwrite());
|
||||||
|
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md").doNotOverwrite());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -594,8 +595,105 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege
|
|||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void postProcessOneOfModels(List<ModelMap> allModels) {
|
||||||
|
final HashMap<String, List<String>> oneOfMapDiscriminator = new HashMap<>();
|
||||||
|
|
||||||
|
for (ModelMap mo : allModels) {
|
||||||
|
final CodegenModel cm = mo.getModel();
|
||||||
|
|
||||||
|
final CodegenComposedSchemas cs = cm.getComposedSchemas();
|
||||||
|
if (cs != null) {
|
||||||
|
final List<CodegenProperty> csOneOf = cs.getOneOf();
|
||||||
|
|
||||||
|
if (csOneOf != null) {
|
||||||
|
for (CodegenProperty model : csOneOf) {
|
||||||
|
// Generate a valid name for the enum variant.
|
||||||
|
// Mainly needed for primitive types.
|
||||||
|
String[] modelParts = model.dataType.replace("<", "Of").replace(">", "").split("::");
|
||||||
|
model.datatypeWithEnum = camelize(modelParts[modelParts.length - 1]);
|
||||||
|
|
||||||
|
// Primitive type is not properly set, this overrides it to guarantee adequate model generation.
|
||||||
|
if (!model.getDataType().matches(String.format(Locale.ROOT, ".*::%s", model.getDatatypeWithEnum()))) {
|
||||||
|
model.isPrimitiveType = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.setOneOf(csOneOf);
|
||||||
|
cm.setComposedSchemas(cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cm.discriminator != null) {
|
||||||
|
for (String model : cm.oneOf) {
|
||||||
|
List<String> discriminators = oneOfMapDiscriminator.getOrDefault(model, new ArrayList<>());
|
||||||
|
discriminators.add(cm.discriminator.getPropertyName());
|
||||||
|
oneOfMapDiscriminator.put(model, discriminators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ModelMap mo : allModels) {
|
||||||
|
final CodegenModel cm = mo.getModel();
|
||||||
|
|
||||||
|
for (CodegenProperty var : cm.vars) {
|
||||||
|
var.isDiscriminator = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> discriminatorsForModel = oneOfMapDiscriminator.get(cm.getSchemaName());
|
||||||
|
|
||||||
|
if (discriminatorsForModel != null) {
|
||||||
|
for (String discriminator : discriminatorsForModel) {
|
||||||
|
boolean hasDiscriminatorDefined = false;
|
||||||
|
|
||||||
|
for (CodegenProperty var : cm.vars) {
|
||||||
|
if (var.baseName.equals(discriminator)) {
|
||||||
|
var.isDiscriminator = true;
|
||||||
|
hasDiscriminatorDefined = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the discriminator field is not a defined attribute in the variant structure, create it.
|
||||||
|
if (!hasDiscriminatorDefined) {
|
||||||
|
CodegenProperty property = new CodegenProperty();
|
||||||
|
|
||||||
|
// Static attributes
|
||||||
|
// Only strings are supported by serde for tag field types, so it's the only one we'll deal with
|
||||||
|
property.openApiType = "string";
|
||||||
|
property.complexType = "string";
|
||||||
|
property.dataType = "String";
|
||||||
|
property.datatypeWithEnum = "String";
|
||||||
|
property.baseType = "string";
|
||||||
|
property.required = true;
|
||||||
|
property.isPrimitiveType = true;
|
||||||
|
property.isString = true;
|
||||||
|
property.isDiscriminator = true;
|
||||||
|
|
||||||
|
// Attributes based on the discriminator value
|
||||||
|
property.baseName = discriminator;
|
||||||
|
property.name = discriminator;
|
||||||
|
property.nameInCamelCase = camelize(discriminator);
|
||||||
|
property.nameInPascalCase = property.nameInCamelCase.substring(0, 1).toUpperCase(Locale.ROOT) + property.nameInCamelCase.substring(1);
|
||||||
|
property.nameInSnakeCase = underscore(discriminator).toUpperCase(Locale.ROOT);
|
||||||
|
property.getter = String.format(Locale.ROOT, "get%s", property.nameInPascalCase);
|
||||||
|
property.setter = String.format(Locale.ROOT, "set%s", property.nameInPascalCase);
|
||||||
|
property.defaultValueWithParam = String.format(Locale.ROOT, " = data.%s;", property.name);
|
||||||
|
|
||||||
|
// Attributes based on the model name
|
||||||
|
property.defaultValue = String.format(Locale.ROOT, "r#\"%s\"#.to_string()", cm.getSchemaName());
|
||||||
|
property.jsonSchema = String.format(Locale.ROOT, "{ \"default\":\"%s\"; \"type\":\"string\" }", cm.getSchemaName());
|
||||||
|
|
||||||
|
cm.vars.add(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OperationsMap postProcessOperationsWithModels(final OperationsMap operationsMap, List<ModelMap> allModels) {
|
public OperationsMap postProcessOperationsWithModels(final OperationsMap operationsMap, List<ModelMap> allModels) {
|
||||||
|
postProcessOneOfModels(allModels);
|
||||||
|
|
||||||
final OperationMap operations = operationsMap.getOperations();
|
final OperationMap operations = operationsMap.getOperations();
|
||||||
operations.put("classnamePascalCase", camelize(operations.getClassname()));
|
operations.put("classnamePascalCase", camelize(operations.getClassname()));
|
||||||
|
|
||||||
|
@ -28,3 +28,6 @@ pub mod apis;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -573,22 +573,71 @@ impl PartialEq for {{{classname}}} {
|
|||||||
self.0.get() == other.0.get()
|
self.0.get() == other.0.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{{/anyOf.size}}
|
{{/anyOf.size}}
|
||||||
{{#oneOf.size}}
|
{{#oneOf.size}}
|
||||||
/// One of:
|
{{#discriminator}}
|
||||||
{{#oneOf}}
|
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||||
/// - {{{.}}}
|
#[serde(tag = "{{{propertyBaseName}}}")]
|
||||||
{{/oneOf}}
|
{{/discriminator}}
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
{{^discriminator}}
|
||||||
pub struct {{{classname}}}(Box<serde_json::value::RawValue>);
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
{{/discriminator}}
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum {{{classname}}} {
|
||||||
|
{{#composedSchemas}}
|
||||||
|
{{#oneOf}}
|
||||||
|
{{{datatypeWithEnum}}}(Box<{{{dataType}}}>),
|
||||||
|
{{/oneOf}}
|
||||||
|
{{/composedSchemas}}
|
||||||
|
}
|
||||||
|
|
||||||
impl validator::Validate for {{{classname}}}
|
impl validator::Validate for {{{classname}}}
|
||||||
{
|
{
|
||||||
fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> {
|
fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> {
|
||||||
std::result::Result::Ok(())
|
match self {
|
||||||
|
{{#composedSchemas}}
|
||||||
|
{{#oneOf}}
|
||||||
|
{{#isPrimitiveType}}
|
||||||
|
Self::{{{datatypeWithEnum}}}(_) => std::result::Result::Ok(()),
|
||||||
|
{{/isPrimitiveType}}
|
||||||
|
{{^isPrimitiveType}}
|
||||||
|
Self::{{{datatypeWithEnum}}}(x) => x.validate(),
|
||||||
|
{{/isPrimitiveType}}
|
||||||
|
{{/oneOf}}
|
||||||
|
{{/composedSchemas}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{{#discriminator}}
|
||||||
|
impl serde::Serialize for {{{classname}}} {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: serde::Serializer {
|
||||||
|
match self {
|
||||||
|
{{#composedSchemas}}
|
||||||
|
{{#oneOf}}
|
||||||
|
Self::{{{datatypeWithEnum}}}(x) => x.serialize(serializer),
|
||||||
|
{{/oneOf}}
|
||||||
|
{{/composedSchemas}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/discriminator}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{{#composedSchemas}}
|
||||||
|
{{#oneOf}}
|
||||||
|
impl From<{{{dataType}}}> for {{{classname}}} {
|
||||||
|
fn from(value: {{{dataType}}}) -> Self {
|
||||||
|
Self::{{{datatypeWithEnum}}}(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/oneOf}}
|
||||||
|
{{/composedSchemas}}
|
||||||
|
|
||||||
/// Converts Query Parameters representation (style=form, explode=false) to a {{{classname}}} value
|
/// Converts Query Parameters representation (style=form, explode=false) to a {{{classname}}} value
|
||||||
/// as specified in https://swagger.io/docs/specification/serialization/
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
/// Should be implemented in a serde deserializer
|
/// Should be implemented in a serde deserializer
|
||||||
@ -600,11 +649,6 @@ impl std::str::FromStr for {{{classname}}} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for {{{classname}}} {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0.get() == other.0.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/oneOf.size}}
|
{{/oneOf.size}}
|
||||||
{{^anyOf.size}}
|
{{^anyOf.size}}
|
||||||
{{^oneOf.size}}
|
{{^oneOf.size}}
|
||||||
@ -613,11 +657,15 @@ impl PartialEq for {{{classname}}} {
|
|||||||
pub struct {{{classname}}} {
|
pub struct {{{classname}}} {
|
||||||
{{#vars}}
|
{{#vars}}
|
||||||
{{#description}}
|
{{#description}}
|
||||||
/// {{{.}}}
|
/// {{{.}}}
|
||||||
{{/description}}
|
{{/description}}
|
||||||
{{#isEnum}}
|
{{#isEnum}}
|
||||||
/// Note: inline enums are not fully supported by openapi-generator
|
/// Note: inline enums are not fully supported by openapi-generator
|
||||||
{{/isEnum}}
|
{{/isEnum}}
|
||||||
|
{{#isDiscriminator}}
|
||||||
|
#[serde(default = "{{{classname}}}::_name_for_{{{name}}}")]
|
||||||
|
#[serde(serialize_with = "{{{classname}}}::_serialize_{{{name}}}")]
|
||||||
|
{{/isDiscriminator}}
|
||||||
#[serde(rename = "{{{baseName}}}")]
|
#[serde(rename = "{{{baseName}}}")]
|
||||||
{{#hasValidation}}
|
{{#hasValidation}}
|
||||||
#[validate(
|
#[validate(
|
||||||
@ -685,6 +733,25 @@ pub struct {{{classname}}} {
|
|||||||
{{/vars}}
|
{{/vars}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{{#vars}}
|
||||||
|
{{#isDiscriminator}}
|
||||||
|
impl {{{classname}}} {
|
||||||
|
fn _name_for_{{{name}}}() -> String {
|
||||||
|
String::from("{{{classname}}}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _serialize_{{{name}}}<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
s.serialize_str(&Self::_name_for_{{{name}}}())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/isDiscriminator}}
|
||||||
|
{{/vars}}
|
||||||
|
|
||||||
|
|
||||||
{{#vars}}
|
{{#vars}}
|
||||||
{{#hasValidation}}
|
{{#hasValidation}}
|
||||||
{{#pattern}}
|
{{#pattern}}
|
||||||
|
9
modules/openapi-generator/src/main/resources/rust-axum/tests.mustache
vendored
Normal file
9
modules/openapi-generator/src/main/resources/rust-axum/tests.mustache
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
openapi: "3.0.4"
|
||||||
|
info:
|
||||||
|
title: "test"
|
||||||
|
version: "0.0.1"
|
||||||
|
paths:
|
||||||
|
"/":
|
||||||
|
post:
|
||||||
|
operationId: foo
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
"application/json":
|
||||||
|
schema:
|
||||||
|
"$ref": "#/components/schemas/Message"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Re-serialize and echo the request data
|
||||||
|
content:
|
||||||
|
"application/json":
|
||||||
|
schema:
|
||||||
|
"$ref": "#/components/schemas/Message"
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Message:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
discriminator:
|
||||||
|
propertyName: op
|
||||||
|
oneOf:
|
||||||
|
- "$ref": "#/components/schemas/Hello"
|
||||||
|
- "$ref": "#/components/schemas/Greeting"
|
||||||
|
- "$ref": "#/components/schemas/Goodbye"
|
||||||
|
title: Message
|
||||||
|
Hello:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
title: Hello
|
||||||
|
properties:
|
||||||
|
op:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- Hello
|
||||||
|
default: Hello
|
||||||
|
d:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
welcome_message:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- welcome_message
|
||||||
|
required:
|
||||||
|
- op
|
||||||
|
- d
|
||||||
|
Greeting:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
title: Greeting
|
||||||
|
properties:
|
||||||
|
d:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
greet_message:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- greet_message
|
||||||
|
required:
|
||||||
|
- d
|
||||||
|
Goodbye:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
title: Goodbye
|
||||||
|
properties:
|
||||||
|
op:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- Goodbye
|
||||||
|
d:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
goodbye_message:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- goodbye_message
|
||||||
|
required:
|
||||||
|
- op
|
||||||
|
- d
|
@ -7,4 +7,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -7,4 +7,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -9,4 +9,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -2764,15 +2764,31 @@ impl std::ops::DerefMut for Ok {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One of:
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
/// - Vec<String>
|
#[serde(untagged)]
|
||||||
/// - i32
|
#[allow(non_camel_case_types)]
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
pub enum OneOfGet200Response {
|
||||||
pub struct OneOfGet200Response(Box<serde_json::value::RawValue>);
|
I32(Box<i32>),
|
||||||
|
VecOfString(Box<Vec<String>>),
|
||||||
|
}
|
||||||
|
|
||||||
impl validator::Validate for OneOfGet200Response {
|
impl validator::Validate for OneOfGet200Response {
|
||||||
fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> {
|
fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> {
|
||||||
std::result::Result::Ok(())
|
match self {
|
||||||
|
Self::I32(_) => std::result::Result::Ok(()),
|
||||||
|
Self::VecOfString(_) => std::result::Result::Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i32> for OneOfGet200Response {
|
||||||
|
fn from(value: i32) -> Self {
|
||||||
|
Self::I32(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Vec<String>> for OneOfGet200Response {
|
||||||
|
fn from(value: Vec<String>) -> Self {
|
||||||
|
Self::VecOfString(Box::new(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2787,12 +2803,6 @@ impl std::str::FromStr for OneOfGet200Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for OneOfGet200Response {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0.get() == other.0.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
|
||||||
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
||||||
pub struct OptionalObjectHeader(i32);
|
pub struct OptionalObjectHeader(i32);
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
#[test]
|
||||||
|
fn test_oneof_schema_untagged() {
|
||||||
|
use crate::models::OneOfGet200Response;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
struct Test {
|
||||||
|
value: OneOfGet200Response,
|
||||||
|
}
|
||||||
|
|
||||||
|
let test0 = r#"{"value": "ignored"}"#;
|
||||||
|
let test1 = r#"{"value": 123}"#;
|
||||||
|
let test2 = r#"{"value": ["foo", "bar"]}"#;
|
||||||
|
|
||||||
|
let test3 = Test {
|
||||||
|
value: OneOfGet200Response::I32(123.into()),
|
||||||
|
};
|
||||||
|
let test4 = Test {
|
||||||
|
value: OneOfGet200Response::VecOfString(vec!["foo".to_string(), "bar".to_string()].into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let test5 = r#"{"value":123}"#;
|
||||||
|
let test6 = r#"{"value":["foo","bar"]}"#;
|
||||||
|
|
||||||
|
assert!(serde_json::from_str::<Test>(test0).is_err());
|
||||||
|
assert!(serde_json::from_str::<Test>(test1).is_ok());
|
||||||
|
assert!(serde_json::from_str::<Test>(test2).is_ok());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test3).expect("Serialization error"),
|
||||||
|
test5
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test4).expect("Serialization error"),
|
||||||
|
test6
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -7,4 +7,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -12,4 +12,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -9,4 +9,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -7,4 +7,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -7,4 +7,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
2
samples/server/petstore/rust-axum/output/rust-axum-oneof/.gitignore
vendored
Normal file
2
samples/server/petstore/rust-axum/output/rust-axum-oneof/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
@ -0,0 +1,23 @@
|
|||||||
|
# OpenAPI Generator Ignore
|
||||||
|
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||||
|
|
||||||
|
# Use this file to prevent files from being overwritten by the generator.
|
||||||
|
# The patterns follow closely to .gitignore or .dockerignore.
|
||||||
|
|
||||||
|
# As an example, the C# client generator defines ApiClient.cs.
|
||||||
|
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||||
|
#ApiClient.cs
|
||||||
|
|
||||||
|
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||||
|
#foo/*/qux
|
||||||
|
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||||
|
#foo/**/qux
|
||||||
|
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can also negate patterns with an exclamation (!).
|
||||||
|
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||||
|
#docs/*.md
|
||||||
|
# Then explicitly reverse the ignore rule for a single file:
|
||||||
|
#!docs/README.md
|
@ -0,0 +1,11 @@
|
|||||||
|
.gitignore
|
||||||
|
Cargo.toml
|
||||||
|
README.md
|
||||||
|
src/apis/default.rs
|
||||||
|
src/apis/mod.rs
|
||||||
|
src/header.rs
|
||||||
|
src/lib.rs
|
||||||
|
src/models.rs
|
||||||
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
|
src/types.rs
|
@ -0,0 +1 @@
|
|||||||
|
7.11.0-SNAPSHOT
|
@ -0,0 +1,46 @@
|
|||||||
|
[package]
|
||||||
|
name = "rust-axum-oneof"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["OpenAPI Generator team and contributors"]
|
||||||
|
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["server"]
|
||||||
|
server = []
|
||||||
|
conversion = [
|
||||||
|
"frunk",
|
||||||
|
"frunk_derives",
|
||||||
|
"frunk_core",
|
||||||
|
"frunk-enum-core",
|
||||||
|
"frunk-enum-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "0.1"
|
||||||
|
axum = "0.7"
|
||||||
|
axum-extra = { version = "0.9", features = ["cookie", "multipart"] }
|
||||||
|
base64 = "0.22"
|
||||||
|
bytes = "1"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
frunk = { version = "0.4", optional = true }
|
||||||
|
frunk-enum-core = { version = "0.3", optional = true }
|
||||||
|
frunk-enum-derive = { version = "0.3", optional = true }
|
||||||
|
frunk_core = { version = "0.4", optional = true }
|
||||||
|
frunk_derives = { version = "0.4", optional = true }
|
||||||
|
http = "1"
|
||||||
|
lazy_static = "1"
|
||||||
|
regex = "1"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = { version = "1", features = ["raw_value"] }
|
||||||
|
serde_urlencoded = "0.7"
|
||||||
|
tokio = { version = "1", default-features = false, features = [
|
||||||
|
"signal",
|
||||||
|
"rt-multi-thread",
|
||||||
|
] }
|
||||||
|
tracing = { version = "0.1", features = ["attributes"] }
|
||||||
|
uuid = { version = "1", features = ["serde"] }
|
||||||
|
validator = { version = "0.19", features = ["derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tracing-subscriber = "0.3"
|
@ -0,0 +1,91 @@
|
|||||||
|
# Rust API for rust-axum-oneof
|
||||||
|
|
||||||
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This server was generated by the [openapi-generator]
|
||||||
|
(https://openapi-generator.tech) project. By using the
|
||||||
|
[OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote
|
||||||
|
server, you can easily generate a server stub.
|
||||||
|
|
||||||
|
To see how to make this your own, look here: [README]((https://openapi-generator.tech))
|
||||||
|
|
||||||
|
- API version: 0.0.1
|
||||||
|
- Generator version: 7.11.0-SNAPSHOT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This autogenerated project defines an API crate `rust-axum-oneof` which contains:
|
||||||
|
* An `Api` trait defining the API in Rust.
|
||||||
|
* Data types representing the underlying data model.
|
||||||
|
* Axum router which accepts HTTP requests and invokes the appropriate `Api` method for each operation.
|
||||||
|
* Request validations (path, query, body params) are included.
|
||||||
|
|
||||||
|
## Using the generated library
|
||||||
|
|
||||||
|
The generated library has a few optional features that can be activated through Cargo.
|
||||||
|
|
||||||
|
* `server`
|
||||||
|
* This defaults to enabled and creates the basic skeleton of a server implementation based on Axum.
|
||||||
|
* To create the server stack you'll need to provide an implementation of the API trait to provide the server function.
|
||||||
|
* `conversions`
|
||||||
|
* This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types.
|
||||||
|
|
||||||
|
See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct ServerImpl {
|
||||||
|
// database: sea_orm::DbConn,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[async_trait]
|
||||||
|
impl rust-axum-oneof::Api for ServerImpl {
|
||||||
|
// API implementation goes here
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_server(addr: &str) {
|
||||||
|
// initialize tracing
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
// Init Axum router
|
||||||
|
let app = rust-axum-oneof::server::new(Arc::new(ServerImpl));
|
||||||
|
|
||||||
|
// Add layers to the router
|
||||||
|
let app = app.layer(...);
|
||||||
|
|
||||||
|
// Run the server with graceful shutdown
|
||||||
|
let listener = TcpListener::bind(addr).await.unwrap();
|
||||||
|
axum::serve(listener, app)
|
||||||
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown_signal() {
|
||||||
|
let ctrl_c = async {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install Ctrl+C handler");
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let terminate = async {
|
||||||
|
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||||
|
.expect("failed to install signal handler")
|
||||||
|
.recv()
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let terminate = std::future::pending::<()>();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = ctrl_c => {},
|
||||||
|
_ = terminate => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,30 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use axum::extract::*;
|
||||||
|
use axum_extra::extract::{CookieJar, Multipart};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use http::Method;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{models, types::*};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum FooResponse {
|
||||||
|
/// Re-serialize and echo the request data
|
||||||
|
Status200_Re(models::Message),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default
|
||||||
|
#[async_trait]
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
|
pub trait Default {
|
||||||
|
/// Foo - POST /
|
||||||
|
async fn foo(
|
||||||
|
&self,
|
||||||
|
method: Method,
|
||||||
|
host: Host,
|
||||||
|
cookies: CookieJar,
|
||||||
|
body: models::Message,
|
||||||
|
) -> Result<FooResponse, ()>;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
pub mod default;
|
@ -0,0 +1,197 @@
|
|||||||
|
use std::{convert::TryFrom, fmt, ops::Deref};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
/// A struct to allow homogeneous conversion into a HeaderValue. We can't
|
||||||
|
/// implement the From/Into trait on HeaderValue because we don't own
|
||||||
|
/// either of the types.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct IntoHeaderValue<T>(pub T);
|
||||||
|
|
||||||
|
// Generic implementations
|
||||||
|
|
||||||
|
impl<T> Deref for IntoHeaderValue<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive for each TryFrom<T> in http::HeaderValue
|
||||||
|
|
||||||
|
macro_rules! ihv_generate {
|
||||||
|
($t:ident) => {
|
||||||
|
impl TryFrom<HeaderValue> for IntoHeaderValue<$t> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
Ok(hdr_value) => match hdr_value.parse::<$t>() {
|
||||||
|
Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value)),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to parse {} as a string: {}",
|
||||||
|
stringify!($t),
|
||||||
|
e
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to parse header {:?} as a string - {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<IntoHeaderValue<$t>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: IntoHeaderValue<$t>) -> Result<Self, Self::Error> {
|
||||||
|
Ok(hdr_value.0.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ihv_generate!(u64);
|
||||||
|
ihv_generate!(i64);
|
||||||
|
ihv_generate!(i16);
|
||||||
|
ihv_generate!(u16);
|
||||||
|
ihv_generate!(u32);
|
||||||
|
ihv_generate!(usize);
|
||||||
|
ihv_generate!(isize);
|
||||||
|
ihv_generate!(i32);
|
||||||
|
|
||||||
|
// Custom derivations
|
||||||
|
|
||||||
|
// Vec<String>
|
||||||
|
|
||||||
|
impl TryFrom<HeaderValue> for IntoHeaderValue<Vec<String>> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
Ok(hdr_value) => Ok(IntoHeaderValue(
|
||||||
|
hdr_value
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|x| match x.trim() {
|
||||||
|
"" => None,
|
||||||
|
y => Some(y.to_string()),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to parse header: {:?} as a string - {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<IntoHeaderValue<Vec<String>>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: IntoHeaderValue<Vec<String>>) -> Result<Self, Self::Error> {
|
||||||
|
match HeaderValue::from_str(&hdr_value.0.join(", ")) {
|
||||||
|
Ok(hdr_value) => Ok(hdr_value),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to convert {:?} into a header - {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String
|
||||||
|
|
||||||
|
impl TryFrom<HeaderValue> for IntoHeaderValue<String> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value.to_string())),
|
||||||
|
Err(e) => Err(format!("Unable to convert header {:?} to {}", hdr_value, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<IntoHeaderValue<String>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: IntoHeaderValue<String>) -> Result<Self, Self::Error> {
|
||||||
|
match HeaderValue::from_str(&hdr_value.0) {
|
||||||
|
Ok(hdr_value) => Ok(hdr_value),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to convert {:?} from a header {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool
|
||||||
|
|
||||||
|
impl TryFrom<HeaderValue> for IntoHeaderValue<bool> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
Ok(hdr_value) => match hdr_value.parse() {
|
||||||
|
Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value)),
|
||||||
|
Err(e) => Err(format!("Unable to parse bool from {} - {}", hdr_value, e)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to convert {:?} from a header {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<IntoHeaderValue<bool>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: IntoHeaderValue<bool>) -> Result<Self, Self::Error> {
|
||||||
|
match HeaderValue::from_str(&hdr_value.0.to_string()) {
|
||||||
|
Ok(hdr_value) => Ok(hdr_value),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to convert: {:?} into a header: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateTime
|
||||||
|
|
||||||
|
impl TryFrom<HeaderValue> for IntoHeaderValue<DateTime<Utc>> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
Ok(hdr_value) => match DateTime::parse_from_rfc3339(hdr_value) {
|
||||||
|
Ok(date) => Ok(IntoHeaderValue(date.with_timezone(&Utc))),
|
||||||
|
Err(e) => Err(format!("Unable to parse: {} as date - {}", hdr_value, e)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to convert header {:?} to string {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<IntoHeaderValue<DateTime<Utc>>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: IntoHeaderValue<DateTime<Utc>>) -> Result<Self, Self::Error> {
|
||||||
|
match HeaderValue::from_str(hdr_value.0.to_rfc3339().as_str()) {
|
||||||
|
Ok(hdr_value) => Ok(hdr_value),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Unable to convert {:?} to a header: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
#![allow(
|
||||||
|
missing_docs,
|
||||||
|
trivial_casts,
|
||||||
|
unused_variables,
|
||||||
|
unused_mut,
|
||||||
|
unused_extern_crates,
|
||||||
|
non_camel_case_types,
|
||||||
|
unused_imports,
|
||||||
|
unused_attributes
|
||||||
|
)]
|
||||||
|
#![allow(
|
||||||
|
clippy::derive_partial_eq_without_eq,
|
||||||
|
clippy::disallowed_names,
|
||||||
|
clippy::too_many_arguments
|
||||||
|
)]
|
||||||
|
|
||||||
|
pub const BASE_PATH: &str = "";
|
||||||
|
pub const API_VERSION: &str = "0.0.1";
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub mod server;
|
||||||
|
|
||||||
|
pub mod apis;
|
||||||
|
pub mod models;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
@ -0,0 +1,985 @@
|
|||||||
|
#![allow(unused_qualifications)]
|
||||||
|
|
||||||
|
use http::HeaderValue;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::header;
|
||||||
|
use crate::{models, types::*};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)]
|
||||||
|
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
||||||
|
pub struct Goodbye {
|
||||||
|
/// Note: inline enums are not fully supported by openapi-generator
|
||||||
|
#[serde(default = "Goodbye::_name_for_op")]
|
||||||
|
#[serde(serialize_with = "Goodbye::_serialize_op")]
|
||||||
|
#[serde(rename = "op")]
|
||||||
|
pub op: String,
|
||||||
|
|
||||||
|
#[serde(rename = "d")]
|
||||||
|
pub d: models::GoodbyeD,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Goodbye {
|
||||||
|
fn _name_for_op() -> String {
|
||||||
|
String::from("Goodbye")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _serialize_op<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
s.serialize_str(&Self::_name_for_op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Goodbye {
|
||||||
|
#[allow(clippy::new_without_default, clippy::too_many_arguments)]
|
||||||
|
pub fn new(op: String, d: models::GoodbyeD) -> Goodbye {
|
||||||
|
Goodbye { op, d }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the Goodbye 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::fmt::Display for Goodbye {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let params: Vec<Option<String>> = vec![
|
||||||
|
Some("op".to_string()),
|
||||||
|
Some(self.op.to_string()),
|
||||||
|
// Skipping d in query parameter serialization
|
||||||
|
];
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
params.into_iter().flatten().collect::<Vec<_>>().join(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Query Parameters representation (style=form, explode=false) to a Goodbye value
|
||||||
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
|
/// Should be implemented in a serde deserializer
|
||||||
|
impl std::str::FromStr for Goodbye {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
/// An intermediate representation of the struct to use for parsing.
|
||||||
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct IntermediateRep {
|
||||||
|
pub op: Vec<String>,
|
||||||
|
pub d: Vec<models::GoodbyeD>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Goodbye".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(key) = key_result {
|
||||||
|
#[allow(clippy::match_single_binding)]
|
||||||
|
match key {
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"op" => intermediate_rep.op.push(
|
||||||
|
<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"d" => intermediate_rep.d.push(
|
||||||
|
<models::GoodbyeD as std::str::FromStr>::from_str(val)
|
||||||
|
.map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
"Unexpected key while parsing Goodbye".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next key
|
||||||
|
key_result = string_iter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the intermediate representation to return the struct
|
||||||
|
std::result::Result::Ok(Goodbye {
|
||||||
|
op: intermediate_rep
|
||||||
|
.op
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "op missing in Goodbye".to_string())?,
|
||||||
|
d: intermediate_rep
|
||||||
|
.d
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "d missing in Goodbye".to_string())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for converting between header::IntoHeaderValue<Goodbye> and HeaderValue
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<header::IntoHeaderValue<Goodbye>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
hdr_value: header::IntoHeaderValue<Goodbye>,
|
||||||
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let hdr_value = hdr_value.to_string();
|
||||||
|
match 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 Goodbye - value: {} is invalid {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<HeaderValue> for header::IntoHeaderValue<Goodbye> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
std::result::Result::Ok(value) => {
|
||||||
|
match <Goodbye as std::str::FromStr>::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 '{}' into Goodbye - {}",
|
||||||
|
value, err
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::result::Result::Err(e) => std::result::Result::Err(format!(
|
||||||
|
"Unable to convert header: {:?} to string: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)]
|
||||||
|
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
||||||
|
pub struct GoodbyeD {
|
||||||
|
#[serde(rename = "goodbye_message")]
|
||||||
|
pub goodbye_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GoodbyeD {
|
||||||
|
#[allow(clippy::new_without_default, clippy::too_many_arguments)]
|
||||||
|
pub fn new(goodbye_message: String) -> GoodbyeD {
|
||||||
|
GoodbyeD { goodbye_message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the GoodbyeD 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::fmt::Display for GoodbyeD {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let params: Vec<Option<String>> = vec![
|
||||||
|
Some("goodbye_message".to_string()),
|
||||||
|
Some(self.goodbye_message.to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
params.into_iter().flatten().collect::<Vec<_>>().join(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Query Parameters representation (style=form, explode=false) to a GoodbyeD value
|
||||||
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
|
/// Should be implemented in a serde deserializer
|
||||||
|
impl std::str::FromStr for GoodbyeD {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
/// An intermediate representation of the struct to use for parsing.
|
||||||
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct IntermediateRep {
|
||||||
|
pub goodbye_message: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 GoodbyeD".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(key) = key_result {
|
||||||
|
#[allow(clippy::match_single_binding)]
|
||||||
|
match key {
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"goodbye_message" => intermediate_rep.goodbye_message.push(
|
||||||
|
<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
"Unexpected key while parsing GoodbyeD".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next key
|
||||||
|
key_result = string_iter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the intermediate representation to return the struct
|
||||||
|
std::result::Result::Ok(GoodbyeD {
|
||||||
|
goodbye_message: intermediate_rep
|
||||||
|
.goodbye_message
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "goodbye_message missing in GoodbyeD".to_string())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for converting between header::IntoHeaderValue<GoodbyeD> and HeaderValue
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<header::IntoHeaderValue<GoodbyeD>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
hdr_value: header::IntoHeaderValue<GoodbyeD>,
|
||||||
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let hdr_value = hdr_value.to_string();
|
||||||
|
match 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 GoodbyeD - value: {} is invalid {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<HeaderValue> for header::IntoHeaderValue<GoodbyeD> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
std::result::Result::Ok(value) => {
|
||||||
|
match <GoodbyeD as std::str::FromStr>::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 '{}' into GoodbyeD - {}",
|
||||||
|
value, err
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::result::Result::Err(e) => std::result::Result::Err(format!(
|
||||||
|
"Unable to convert header: {:?} to string: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)]
|
||||||
|
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
||||||
|
pub struct Greeting {
|
||||||
|
#[serde(rename = "d")]
|
||||||
|
pub d: models::GreetingD,
|
||||||
|
|
||||||
|
#[serde(default = "Greeting::_name_for_op")]
|
||||||
|
#[serde(serialize_with = "Greeting::_serialize_op")]
|
||||||
|
#[serde(rename = "op")]
|
||||||
|
pub op: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Greeting {
|
||||||
|
fn _name_for_op() -> String {
|
||||||
|
String::from("Greeting")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _serialize_op<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
s.serialize_str(&Self::_name_for_op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Greeting {
|
||||||
|
#[allow(clippy::new_without_default, clippy::too_many_arguments)]
|
||||||
|
pub fn new(d: models::GreetingD) -> Greeting {
|
||||||
|
Greeting {
|
||||||
|
d,
|
||||||
|
op: r#"Greeting"#.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the Greeting 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::fmt::Display for Greeting {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let params: Vec<Option<String>> = vec![
|
||||||
|
// Skipping d in query parameter serialization
|
||||||
|
Some("op".to_string()),
|
||||||
|
Some(self.op.to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
params.into_iter().flatten().collect::<Vec<_>>().join(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Query Parameters representation (style=form, explode=false) to a Greeting value
|
||||||
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
|
/// Should be implemented in a serde deserializer
|
||||||
|
impl std::str::FromStr for Greeting {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
/// An intermediate representation of the struct to use for parsing.
|
||||||
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct IntermediateRep {
|
||||||
|
pub d: Vec<models::GreetingD>,
|
||||||
|
pub op: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Greeting".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(key) = key_result {
|
||||||
|
#[allow(clippy::match_single_binding)]
|
||||||
|
match key {
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"d" => intermediate_rep.d.push(
|
||||||
|
<models::GreetingD as std::str::FromStr>::from_str(val)
|
||||||
|
.map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"op" => intermediate_rep.op.push(
|
||||||
|
<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
"Unexpected key while parsing Greeting".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next key
|
||||||
|
key_result = string_iter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the intermediate representation to return the struct
|
||||||
|
std::result::Result::Ok(Greeting {
|
||||||
|
d: intermediate_rep
|
||||||
|
.d
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "d missing in Greeting".to_string())?,
|
||||||
|
op: intermediate_rep
|
||||||
|
.op
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "op missing in Greeting".to_string())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for converting between header::IntoHeaderValue<Greeting> and HeaderValue
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<header::IntoHeaderValue<Greeting>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
hdr_value: header::IntoHeaderValue<Greeting>,
|
||||||
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let hdr_value = hdr_value.to_string();
|
||||||
|
match 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 Greeting - value: {} is invalid {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<HeaderValue> for header::IntoHeaderValue<Greeting> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
std::result::Result::Ok(value) => {
|
||||||
|
match <Greeting as std::str::FromStr>::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 '{}' into Greeting - {}",
|
||||||
|
value, err
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::result::Result::Err(e) => std::result::Result::Err(format!(
|
||||||
|
"Unable to convert header: {:?} to string: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)]
|
||||||
|
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
||||||
|
pub struct GreetingD {
|
||||||
|
#[serde(rename = "greet_message")]
|
||||||
|
pub greet_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GreetingD {
|
||||||
|
#[allow(clippy::new_without_default, clippy::too_many_arguments)]
|
||||||
|
pub fn new(greet_message: String) -> GreetingD {
|
||||||
|
GreetingD { greet_message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the GreetingD 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::fmt::Display for GreetingD {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let params: Vec<Option<String>> = vec![
|
||||||
|
Some("greet_message".to_string()),
|
||||||
|
Some(self.greet_message.to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
params.into_iter().flatten().collect::<Vec<_>>().join(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Query Parameters representation (style=form, explode=false) to a GreetingD value
|
||||||
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
|
/// Should be implemented in a serde deserializer
|
||||||
|
impl std::str::FromStr for GreetingD {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
/// An intermediate representation of the struct to use for parsing.
|
||||||
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct IntermediateRep {
|
||||||
|
pub greet_message: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 GreetingD".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(key) = key_result {
|
||||||
|
#[allow(clippy::match_single_binding)]
|
||||||
|
match key {
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"greet_message" => intermediate_rep.greet_message.push(
|
||||||
|
<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
"Unexpected key while parsing GreetingD".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next key
|
||||||
|
key_result = string_iter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the intermediate representation to return the struct
|
||||||
|
std::result::Result::Ok(GreetingD {
|
||||||
|
greet_message: intermediate_rep
|
||||||
|
.greet_message
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "greet_message missing in GreetingD".to_string())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for converting between header::IntoHeaderValue<GreetingD> and HeaderValue
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<header::IntoHeaderValue<GreetingD>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
hdr_value: header::IntoHeaderValue<GreetingD>,
|
||||||
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let hdr_value = hdr_value.to_string();
|
||||||
|
match 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 GreetingD - value: {} is invalid {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<HeaderValue> for header::IntoHeaderValue<GreetingD> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
std::result::Result::Ok(value) => {
|
||||||
|
match <GreetingD as std::str::FromStr>::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 '{}' into GreetingD - {}",
|
||||||
|
value, err
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::result::Result::Err(e) => std::result::Result::Err(format!(
|
||||||
|
"Unable to convert header: {:?} to string: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)]
|
||||||
|
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
||||||
|
pub struct Hello {
|
||||||
|
/// Note: inline enums are not fully supported by openapi-generator
|
||||||
|
#[serde(default = "Hello::_name_for_op")]
|
||||||
|
#[serde(serialize_with = "Hello::_serialize_op")]
|
||||||
|
#[serde(rename = "op")]
|
||||||
|
pub op: String,
|
||||||
|
|
||||||
|
#[serde(rename = "d")]
|
||||||
|
pub d: models::HelloD,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hello {
|
||||||
|
fn _name_for_op() -> String {
|
||||||
|
String::from("Hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _serialize_op<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
s.serialize_str(&Self::_name_for_op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hello {
|
||||||
|
#[allow(clippy::new_without_default, clippy::too_many_arguments)]
|
||||||
|
pub fn new(d: models::HelloD) -> Hello {
|
||||||
|
Hello {
|
||||||
|
op: r#"Hello"#.to_string(),
|
||||||
|
d,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the Hello 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::fmt::Display for Hello {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let params: Vec<Option<String>> = vec![
|
||||||
|
Some("op".to_string()),
|
||||||
|
Some(self.op.to_string()),
|
||||||
|
// Skipping d in query parameter serialization
|
||||||
|
];
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
params.into_iter().flatten().collect::<Vec<_>>().join(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Query Parameters representation (style=form, explode=false) to a Hello value
|
||||||
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
|
/// Should be implemented in a serde deserializer
|
||||||
|
impl std::str::FromStr for Hello {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
/// An intermediate representation of the struct to use for parsing.
|
||||||
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct IntermediateRep {
|
||||||
|
pub op: Vec<String>,
|
||||||
|
pub d: Vec<models::HelloD>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Hello".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(key) = key_result {
|
||||||
|
#[allow(clippy::match_single_binding)]
|
||||||
|
match key {
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"op" => intermediate_rep.op.push(
|
||||||
|
<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"d" => intermediate_rep.d.push(
|
||||||
|
<models::HelloD as std::str::FromStr>::from_str(val)
|
||||||
|
.map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
"Unexpected key while parsing Hello".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next key
|
||||||
|
key_result = string_iter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the intermediate representation to return the struct
|
||||||
|
std::result::Result::Ok(Hello {
|
||||||
|
op: intermediate_rep
|
||||||
|
.op
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "op missing in Hello".to_string())?,
|
||||||
|
d: intermediate_rep
|
||||||
|
.d
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "d missing in Hello".to_string())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for converting between header::IntoHeaderValue<Hello> and HeaderValue
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<header::IntoHeaderValue<Hello>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
hdr_value: header::IntoHeaderValue<Hello>,
|
||||||
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let hdr_value = hdr_value.to_string();
|
||||||
|
match 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 Hello - value: {} is invalid {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<HeaderValue> for header::IntoHeaderValue<Hello> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
std::result::Result::Ok(value) => match <Hello as std::str::FromStr>::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 '{}' into Hello - {}",
|
||||||
|
value, err
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
std::result::Result::Err(e) => std::result::Result::Err(format!(
|
||||||
|
"Unable to convert header: {:?} to string: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)]
|
||||||
|
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
|
||||||
|
pub struct HelloD {
|
||||||
|
#[serde(rename = "welcome_message")]
|
||||||
|
pub welcome_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HelloD {
|
||||||
|
#[allow(clippy::new_without_default, clippy::too_many_arguments)]
|
||||||
|
pub fn new(welcome_message: String) -> HelloD {
|
||||||
|
HelloD { welcome_message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the HelloD 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::fmt::Display for HelloD {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let params: Vec<Option<String>> = vec![
|
||||||
|
Some("welcome_message".to_string()),
|
||||||
|
Some(self.welcome_message.to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
params.into_iter().flatten().collect::<Vec<_>>().join(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Query Parameters representation (style=form, explode=false) to a HelloD value
|
||||||
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
|
/// Should be implemented in a serde deserializer
|
||||||
|
impl std::str::FromStr for HelloD {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
/// An intermediate representation of the struct to use for parsing.
|
||||||
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct IntermediateRep {
|
||||||
|
pub welcome_message: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 HelloD".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(key) = key_result {
|
||||||
|
#[allow(clippy::match_single_binding)]
|
||||||
|
match key {
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
"welcome_message" => intermediate_rep.welcome_message.push(
|
||||||
|
<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
"Unexpected key while parsing HelloD".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next key
|
||||||
|
key_result = string_iter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the intermediate representation to return the struct
|
||||||
|
std::result::Result::Ok(HelloD {
|
||||||
|
welcome_message: intermediate_rep
|
||||||
|
.welcome_message
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "welcome_message missing in HelloD".to_string())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for converting between header::IntoHeaderValue<HelloD> and HeaderValue
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<header::IntoHeaderValue<HelloD>> for HeaderValue {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
hdr_value: header::IntoHeaderValue<HelloD>,
|
||||||
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let hdr_value = hdr_value.to_string();
|
||||||
|
match 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 HelloD - value: {} is invalid {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl std::convert::TryFrom<HeaderValue> for header::IntoHeaderValue<HelloD> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(hdr_value: HeaderValue) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match hdr_value.to_str() {
|
||||||
|
std::result::Result::Ok(value) => {
|
||||||
|
match <HelloD as std::str::FromStr>::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 '{}' into HelloD - {}",
|
||||||
|
value, err
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::result::Result::Err(e) => std::result::Result::Err(format!(
|
||||||
|
"Unable to convert header: {:?} to string: {}",
|
||||||
|
hdr_value, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||||
|
#[serde(tag = "op")]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum Message {
|
||||||
|
Hello(Box<models::Hello>),
|
||||||
|
Greeting(Box<models::Greeting>),
|
||||||
|
Goodbye(Box<models::Goodbye>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl validator::Validate for Message {
|
||||||
|
fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> {
|
||||||
|
match self {
|
||||||
|
Self::Hello(x) => x.validate(),
|
||||||
|
Self::Greeting(x) => x.validate(),
|
||||||
|
Self::Goodbye(x) => x.validate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for Message {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Hello(x) => x.serialize(serializer),
|
||||||
|
Self::Greeting(x) => x.serialize(serializer),
|
||||||
|
Self::Goodbye(x) => x.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<models::Hello> for Message {
|
||||||
|
fn from(value: models::Hello) -> Self {
|
||||||
|
Self::Hello(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<models::Greeting> for Message {
|
||||||
|
fn from(value: models::Greeting) -> Self {
|
||||||
|
Self::Greeting(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<models::Goodbye> for Message {
|
||||||
|
fn from(value: models::Goodbye) -> Self {
|
||||||
|
Self::Goodbye(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Query Parameters representation (style=form, explode=false) to a Message value
|
||||||
|
/// as specified in https://swagger.io/docs/specification/serialization/
|
||||||
|
/// Should be implemented in a serde deserializer
|
||||||
|
impl std::str::FromStr for Message {
|
||||||
|
type Err = serde_json::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
serde_json::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use axum::{body::Body, extract::*, response::Response, routing::*};
|
||||||
|
use axum_extra::extract::{CookieJar, Multipart};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use http::{header::CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue, Method, StatusCode};
|
||||||
|
use tracing::error;
|
||||||
|
use validator::{Validate, ValidationErrors};
|
||||||
|
|
||||||
|
use crate::{header, types::*};
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use crate::{apis, models};
|
||||||
|
|
||||||
|
/// Setup API Server.
|
||||||
|
pub fn new<I, A>(api_impl: I) -> Router
|
||||||
|
where
|
||||||
|
I: AsRef<A> + Clone + Send + Sync + 'static,
|
||||||
|
A: apis::default::Default + 'static,
|
||||||
|
{
|
||||||
|
// build our application with a route
|
||||||
|
Router::new()
|
||||||
|
.route("/", post(foo::<I, A>))
|
||||||
|
.with_state(api_impl)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(validator::Validate)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct FooBodyValidator<'a> {
|
||||||
|
#[validate(nested)]
|
||||||
|
body: &'a models::Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn foo_validation(
|
||||||
|
body: models::Message,
|
||||||
|
) -> std::result::Result<(models::Message,), ValidationErrors> {
|
||||||
|
let b = FooBodyValidator { body: &body };
|
||||||
|
b.validate()?;
|
||||||
|
|
||||||
|
Ok((body,))
|
||||||
|
}
|
||||||
|
/// Foo - POST /
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn foo<I, A>(
|
||||||
|
method: Method,
|
||||||
|
host: Host,
|
||||||
|
cookies: CookieJar,
|
||||||
|
State(api_impl): State<I>,
|
||||||
|
Json(body): Json<models::Message>,
|
||||||
|
) -> Result<Response, StatusCode>
|
||||||
|
where
|
||||||
|
I: AsRef<A> + Send + Sync,
|
||||||
|
A: apis::default::Default,
|
||||||
|
{
|
||||||
|
#[allow(clippy::redundant_closure)]
|
||||||
|
let validation = tokio::task::spawn_blocking(move || foo_validation(body))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let Ok((body,)) = validation else {
|
||||||
|
return Response::builder()
|
||||||
|
.status(StatusCode::BAD_REQUEST)
|
||||||
|
.body(Body::from(validation.unwrap_err().to_string()))
|
||||||
|
.map_err(|_| StatusCode::BAD_REQUEST);
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = api_impl.as_ref().foo(method, host, cookies, body).await;
|
||||||
|
|
||||||
|
let mut response = Response::builder();
|
||||||
|
|
||||||
|
let resp = match result {
|
||||||
|
Ok(rsp) => match rsp {
|
||||||
|
apis::default::FooResponse::Status200_Re(body) => {
|
||||||
|
let mut response = response.status(200);
|
||||||
|
{
|
||||||
|
let mut response_headers = response.headers_mut().unwrap();
|
||||||
|
response_headers.insert(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
HeaderValue::from_str("application/json").map_err(|e| {
|
||||||
|
error!(error = ?e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let body_content = tokio::task::spawn_blocking(move || {
|
||||||
|
serde_json::to_vec(&body).map_err(|e| {
|
||||||
|
error!(error = ?e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()?;
|
||||||
|
response.body(Body::from(body_content))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
// Application code returned an error. This should not happen, as the implementation should
|
||||||
|
// return a valid response.
|
||||||
|
response.status(500).body(Body::empty())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resp.map_err(|e| {
|
||||||
|
error!(error = ?e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
#[test]
|
||||||
|
fn test_oneof_schema_with_discriminator() {
|
||||||
|
use crate::models::*;
|
||||||
|
|
||||||
|
let test0 = r#"{"op": "ignored", "d": {"welcome_message": "test0"}}"#;
|
||||||
|
|
||||||
|
let test1 = r#"{"op": "Hello", "d": {"welcome_message": "test1"}}"#;
|
||||||
|
let test2 = r#"{"op": "Greeting", "d": {"greet_message": "test2"}}"#;
|
||||||
|
let test3 = r#"{"op": "Goodbye", "d": {"goodbye_message": "test3"}}"#;
|
||||||
|
|
||||||
|
let test4 = Hello {
|
||||||
|
op: "ignored".to_string(),
|
||||||
|
d: HelloD {
|
||||||
|
welcome_message: "test4".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let test5 = Greeting {
|
||||||
|
op: "ignored".to_string(),
|
||||||
|
d: GreetingD {
|
||||||
|
greet_message: "test5".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let test6 = Goodbye {
|
||||||
|
op: "ignored".to_string(),
|
||||||
|
d: GoodbyeD {
|
||||||
|
goodbye_message: "test6".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let test7 = Message::Hello(test4.clone().into());
|
||||||
|
let test8 = Message::Greeting(test5.clone().into());
|
||||||
|
let test9 = Message::Goodbye(test6.clone().into());
|
||||||
|
|
||||||
|
let test10: Message = test4.clone().into();
|
||||||
|
let test11: Message = test5.clone().into();
|
||||||
|
let test12: Message = test6.clone().into();
|
||||||
|
|
||||||
|
let test13 = r#"{"op":"Hello","d":{"welcome_message":"test4"}}"#;
|
||||||
|
let test14 = r#"{"d":{"greet_message":"test5"},"op":"Greeting"}"#;
|
||||||
|
let test15 = r#"{"op":"Goodbye","d":{"goodbye_message":"test6"}}"#;
|
||||||
|
|
||||||
|
assert!(serde_json::from_str::<Message>(test0).is_err());
|
||||||
|
|
||||||
|
assert!(serde_json::from_str::<Hello>(test0).is_ok());
|
||||||
|
assert!(serde_json::from_str::<Greeting>(test0).is_err());
|
||||||
|
assert!(serde_json::from_str::<Goodbye>(test0).is_err());
|
||||||
|
|
||||||
|
assert!(serde_json::from_str::<Message>(test1).is_ok());
|
||||||
|
assert!(serde_json::from_str::<Message>(test2).is_ok());
|
||||||
|
assert!(serde_json::from_str::<Message>(test3).is_ok());
|
||||||
|
|
||||||
|
assert!(serde_json::from_str::<Hello>(test1).is_ok());
|
||||||
|
assert!(serde_json::from_str::<Greeting>(test2).is_ok());
|
||||||
|
assert!(serde_json::from_str::<Message>(test3).is_ok());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test4).expect("Serialization error"),
|
||||||
|
test13
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test5).expect("Serialization error"),
|
||||||
|
test14
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test6).expect("Serialization error"),
|
||||||
|
test15
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test7).expect("Serialization error"),
|
||||||
|
test13
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test8).expect("Serialization error"),
|
||||||
|
test14
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test9).expect("Serialization error"),
|
||||||
|
test15
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test10).expect("Serialization error"),
|
||||||
|
test13
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test11).expect("Serialization error"),
|
||||||
|
test14
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&test12).expect("Serialization error"),
|
||||||
|
test15
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -0,0 +1,790 @@
|
|||||||
|
use std::{mem, str::FromStr};
|
||||||
|
|
||||||
|
use base64::{engine::general_purpose, Engine};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Object(serde_json::Value);
|
||||||
|
|
||||||
|
impl validator::Validate for Object {
|
||||||
|
fn validate(&self) -> Result<(), validator::ValidationErrors> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Object {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self(serde_json::Value::String(s.to_owned())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serde helper function to create a default `Option<Nullable<T>>` while
|
||||||
|
/// deserializing
|
||||||
|
pub fn default_optional_nullable<T>() -> Option<Nullable<T>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serde helper function to deserialize into an `Option<Nullable<T>>`
|
||||||
|
pub fn deserialize_optional_nullable<'de, D, T>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Option<Nullable<T>>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
T: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
Option::<T>::deserialize(deserializer).map(|val| match val {
|
||||||
|
Some(inner) => Some(Nullable::Present(inner)),
|
||||||
|
None => Some(Nullable::Null),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Nullable type. Represents a value which may be specified as null on an API.
|
||||||
|
/// Note that this is distinct from a value that is optional and not present!
|
||||||
|
///
|
||||||
|
/// Nullable implements many of the same methods as the Option type (map, unwrap, etc).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
pub enum Nullable<T> {
|
||||||
|
/// Null value
|
||||||
|
Null,
|
||||||
|
/// Value is present
|
||||||
|
Present(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Nullable<T> {
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Querying the contained values
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns `true` if the Nullable is a `Present` value.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x: Nullable<u32> = Nullable::Present(2);
|
||||||
|
/// assert_eq!(x.is_present(), true);
|
||||||
|
///
|
||||||
|
/// let x: Nullable<u32> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.is_present(), false);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_present(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Nullable::Present(_) => true,
|
||||||
|
Nullable::Null => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the Nullable is a `Null` value.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x: Nullable<u32> = Nullable::Present(2);
|
||||||
|
/// assert_eq!(x.is_null(), false);
|
||||||
|
///
|
||||||
|
/// let x: Nullable<u32> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.is_null(), true);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
!self.is_present()
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Adapter for working with references
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Converts from `Nullable<T>` to `Nullable<&T>`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Convert an `Nullable<`[`String`]`>` into a `Nullable<`[`usize`]`>`, preserving the original.
|
||||||
|
/// The [`map`] method takes the `self` argument by value, consuming the original,
|
||||||
|
/// so this technique uses `as_ref` to first take a `Nullable` to a reference
|
||||||
|
/// to the value inside the original.
|
||||||
|
///
|
||||||
|
/// [`map`]: enum.Nullable.html#method.map
|
||||||
|
/// [`String`]: ../../std/string/struct.String.html
|
||||||
|
/// [`usize`]: ../../std/primitive.usize.html
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let num_as_str: Nullable<String> = Nullable::Present("10".to_string());
|
||||||
|
/// // First, cast `Nullable<String>` to `Nullable<&String>` with `as_ref`,
|
||||||
|
/// // then consume *that* with `map`, leaving `num_as_str` on the stack.
|
||||||
|
/// let num_as_int: Nullable<usize> = num_as_str.as_ref().map(|n| n.len());
|
||||||
|
/// println!("still can print num_as_str: {:?}", num_as_str);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn as_ref(&self) -> Nullable<&T> {
|
||||||
|
match *self {
|
||||||
|
Nullable::Present(ref x) => Nullable::Present(x),
|
||||||
|
Nullable::Null => Nullable::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts from `Nullable<T>` to `Nullable<&mut T>`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let mut x = Nullable::Present(2);
|
||||||
|
/// match x.as_mut() {
|
||||||
|
/// Nullable::Present(v) => *v = 42,
|
||||||
|
/// Nullable::Null => {},
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(x, Nullable::Present(42));
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn as_mut(&mut self) -> Nullable<&mut T> {
|
||||||
|
match *self {
|
||||||
|
Nullable::Present(ref mut x) => Nullable::Present(x),
|
||||||
|
Nullable::Null => Nullable::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Getting to contained values
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Unwraps a Nullable, yielding the content of a `Nullable::Present`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the value is a [`Nullable::Null`] with a custom panic message provided by
|
||||||
|
/// `msg`.
|
||||||
|
///
|
||||||
|
/// [`Nullable::Null`]: #variant.Null
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present("value");
|
||||||
|
/// assert_eq!(x.expect("the world is ending"), "value");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x: Nullable<&str> = Nullable::Null;
|
||||||
|
/// x.expect("the world is ending"); // panics with `the world is ending`
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn expect(self, msg: &str) -> T {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(val) => val,
|
||||||
|
Nullable::Null => expect_failed(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the value `v` out of the `Nullable<T>` if it is `Nullable::Present(v)`.
|
||||||
|
///
|
||||||
|
/// In general, because this function may panic, its use is discouraged.
|
||||||
|
/// Instead, prefer to use pattern matching and handle the `Nullable::Null`
|
||||||
|
/// case explicitly.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the self value equals [`Nullable::Null`].
|
||||||
|
///
|
||||||
|
/// [`Nullable::Null`]: #variant.Null
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present("air");
|
||||||
|
/// assert_eq!(x.unwrap(), "air");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x: Nullable<&str> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.unwrap(), "air"); // fails
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn unwrap(self) -> T {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(val) => val,
|
||||||
|
Nullable::Null => panic!("called `Nullable::unwrap()` on a `Nullable::Null` value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the contained value or a default.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// assert_eq!(Nullable::Present("car").unwrap_or("bike"), "car");
|
||||||
|
/// assert_eq!(Nullable::Null.unwrap_or("bike"), "bike");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn unwrap_or(self, def: T) -> T {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(x) => x,
|
||||||
|
Nullable::Null => def,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the contained value or computes it from a closure.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let k = 10;
|
||||||
|
/// assert_eq!(Nullable::Present(4).unwrap_or_else(|| 2 * k), 4);
|
||||||
|
/// assert_eq!(Nullable::Null.unwrap_or_else(|| 2 * k), 20);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(x) => x,
|
||||||
|
Nullable::Null => f(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Transforming contained values
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Maps a `Nullable<T>` to `Nullable<U>` by applying a function to a contained value.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Convert a `Nullable<`[`String`]`>` into a `Nullable<`[`usize`]`>`, consuming the original:
|
||||||
|
///
|
||||||
|
/// [`String`]: ../../std/string/struct.String.html
|
||||||
|
/// [`usize`]: ../../std/primitive.usize.html
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let maybe_some_string = Nullable::Present(String::from("Hello, World!"));
|
||||||
|
/// // `Nullable::map` takes self *by value*, consuming `maybe_some_string`
|
||||||
|
/// let maybe_some_len = maybe_some_string.map(|s| s.len());
|
||||||
|
///
|
||||||
|
/// assert_eq!(maybe_some_len, Nullable::Present(13));
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Nullable<U> {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(x) => Nullable::Present(f(x)),
|
||||||
|
Nullable::Null => Nullable::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a function to the contained value (if any),
|
||||||
|
/// or returns a `default` (if not).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present("foo");
|
||||||
|
/// assert_eq!(x.map_or(42, |v| v.len()), 3);
|
||||||
|
///
|
||||||
|
/// let x: Nullable<&str> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.map_or(42, |v| v.len()), 42);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(t) => f(t),
|
||||||
|
Nullable::Null => default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a function to the contained value (if any),
|
||||||
|
/// or computes a `default` (if not).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let k = 21;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present("foo");
|
||||||
|
/// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3);
|
||||||
|
///
|
||||||
|
/// let x: Nullable<&str> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(t) => f(t),
|
||||||
|
Nullable::Null => default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms the `Nullable<T>` into a [`Result<T, E>`], mapping `Nullable::Present(v)` to
|
||||||
|
/// [`Ok(v)`] and `Nullable::Null` to [`Err(err)`][Err].
|
||||||
|
///
|
||||||
|
/// [`Result<T, E>`]: ../../std/result/enum.Result.html
|
||||||
|
/// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok
|
||||||
|
/// [Err]: ../../std/result/enum.Result.html#variant.Err
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present("foo");
|
||||||
|
/// assert_eq!(x.ok_or(0), Ok("foo"));
|
||||||
|
///
|
||||||
|
/// let x: Nullable<&str> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.ok_or(0), Err(0));
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(v) => Ok(v),
|
||||||
|
Nullable::Null => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms the `Nullable<T>` into a [`Result<T, E>`], mapping `Nullable::Present(v)` to
|
||||||
|
/// [`Ok(v)`] and `Nullable::Null` to [`Err(err())`][Err].
|
||||||
|
///
|
||||||
|
/// [`Result<T, E>`]: ../../std/result/enum.Result.html
|
||||||
|
/// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok
|
||||||
|
/// [Err]: ../../std/result/enum.Result.html#variant.Err
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present("foo");
|
||||||
|
/// assert_eq!(x.ok_or_else(|| 0), Ok("foo"));
|
||||||
|
///
|
||||||
|
/// let x: Nullable<&str> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.ok_or_else(|| 0), Err(0));
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(v) => Ok(v),
|
||||||
|
Nullable::Null => Err(err()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Boolean operations on the values, eager and lazy
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns `Nullable::Null` if the Nullable is `Nullable::Null`, otherwise returns `optb`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present(2);
|
||||||
|
/// let y: Nullable<&str> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.and(y), Nullable::Null);
|
||||||
|
///
|
||||||
|
/// let x: Nullable<u32> = Nullable::Null;
|
||||||
|
/// let y = Nullable::Present("foo");
|
||||||
|
/// assert_eq!(x.and(y), Nullable::Null);
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present(2);
|
||||||
|
/// let y = Nullable::Present("foo");
|
||||||
|
/// assert_eq!(x.and(y), Nullable::Present("foo"));
|
||||||
|
///
|
||||||
|
/// let x: Nullable<u32> = Nullable::Null;
|
||||||
|
/// let y: Nullable<&str> = Nullable::Null;
|
||||||
|
/// assert_eq!(x.and(y), Nullable::Null);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn and<U>(self, optb: Nullable<U>) -> Nullable<U> {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(_) => optb,
|
||||||
|
Nullable::Null => Nullable::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `Nullable::Null` if the Nullable is `Nullable::Null`, otherwise calls `f` with the
|
||||||
|
/// wrapped value and returns the result.
|
||||||
|
///
|
||||||
|
/// Some languages call this operation flatmap.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// fn sq(x: u32) -> Nullable<u32> { Nullable::Present(x * x) }
|
||||||
|
/// fn nope(_: u32) -> Nullable<u32> { Nullable::Null }
|
||||||
|
///
|
||||||
|
/// assert_eq!(Nullable::Present(2).and_then(sq).and_then(sq), Nullable::Present(16));
|
||||||
|
/// assert_eq!(Nullable::Present(2).and_then(sq).and_then(nope), Nullable::Null);
|
||||||
|
/// assert_eq!(Nullable::Present(2).and_then(nope).and_then(sq), Nullable::Null);
|
||||||
|
/// assert_eq!(Nullable::Null.and_then(sq).and_then(sq), Nullable::Null);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn and_then<U, F: FnOnce(T) -> Nullable<U>>(self, f: F) -> Nullable<U> {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(x) => f(x),
|
||||||
|
Nullable::Null => Nullable::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Nullable if it contains a value, otherwise returns `optb`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present(2);
|
||||||
|
/// let y = Nullable::Null;
|
||||||
|
/// assert_eq!(x.or(y), Nullable::Present(2));
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Null;
|
||||||
|
/// let y = Nullable::Present(100);
|
||||||
|
/// assert_eq!(x.or(y), Nullable::Present(100));
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present(2);
|
||||||
|
/// let y = Nullable::Present(100);
|
||||||
|
/// assert_eq!(x.or(y), Nullable::Present(2));
|
||||||
|
///
|
||||||
|
/// let x: Nullable<u32> = Nullable::Null;
|
||||||
|
/// let y = Nullable::Null;
|
||||||
|
/// assert_eq!(x.or(y), Nullable::Null);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn or(self, optb: Nullable<T>) -> Nullable<T> {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(_) => self,
|
||||||
|
Nullable::Null => optb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Nullable if it contains a value, otherwise calls `f` and
|
||||||
|
/// returns the result.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// fn nobody() -> Nullable<&'static str> { Nullable::Null }
|
||||||
|
/// fn vikings() -> Nullable<&'static str> { Nullable::Present("vikings") }
|
||||||
|
///
|
||||||
|
/// assert_eq!(Nullable::Present("barbarians").or_else(vikings),
|
||||||
|
/// Nullable::Present("barbarians"));
|
||||||
|
/// assert_eq!(Nullable::Null.or_else(vikings), Nullable::Present("vikings"));
|
||||||
|
/// assert_eq!(Nullable::Null.or_else(nobody), Nullable::Null);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn or_else<F: FnOnce() -> Nullable<T>>(self, f: F) -> Nullable<T> {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(_) => self,
|
||||||
|
Nullable::Null => f(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Misc
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Takes the value out of the Nullable, leaving a `Nullable::Null` in its place.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let mut x = Nullable::Present(2);
|
||||||
|
/// x.take();
|
||||||
|
/// assert_eq!(x, Nullable::Null);
|
||||||
|
///
|
||||||
|
/// let mut x: Nullable<u32> = Nullable::Null;
|
||||||
|
/// x.take();
|
||||||
|
/// assert_eq!(x, Nullable::Null);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn take(&mut self) -> Nullable<T> {
|
||||||
|
mem::replace(self, Nullable::Null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Nullable<&T> {
|
||||||
|
/// Maps an `Nullable<&T>` to an `Nullable<T>` by cloning the contents of the
|
||||||
|
/// Nullable.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = 12;
|
||||||
|
/// let opt_x = Nullable::Present(&x);
|
||||||
|
/// assert_eq!(opt_x, Nullable::Present(&12));
|
||||||
|
/// let cloned = opt_x.cloned();
|
||||||
|
/// assert_eq!(cloned, Nullable::Present(12));
|
||||||
|
/// ```
|
||||||
|
pub fn cloned(self) -> Nullable<T> {
|
||||||
|
self.map(Clone::clone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default> Nullable<T> {
|
||||||
|
/// Returns the contained value or a default
|
||||||
|
///
|
||||||
|
/// Consumes the `self` argument then, if `Nullable::Present`, returns the contained
|
||||||
|
/// value, otherwise if `Nullable::Null`, returns the default value for that
|
||||||
|
/// type.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rust_axum_oneof::types::Nullable;
|
||||||
|
///
|
||||||
|
/// let x = Nullable::Present(42);
|
||||||
|
/// assert_eq!(42, x.unwrap_or_default());
|
||||||
|
///
|
||||||
|
/// let y: Nullable<i32> = Nullable::Null;
|
||||||
|
/// assert_eq!(0, y.unwrap_or_default());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn unwrap_or_default(self) -> T {
|
||||||
|
match self {
|
||||||
|
Nullable::Present(x) => x,
|
||||||
|
Nullable::Null => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Nullable<T> {
|
||||||
|
/// Returns None.
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Nullable<T> {
|
||||||
|
Nullable::Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for Nullable<T> {
|
||||||
|
fn from(val: T) -> Nullable<T> {
|
||||||
|
Nullable::Present(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Serialize for Nullable<T>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Nullable::Present(ref inner) => serializer.serialize_some(&inner),
|
||||||
|
Nullable::Null => serializer.serialize_none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T> Deserialize<'de> for Nullable<T>
|
||||||
|
where
|
||||||
|
T: serde::de::DeserializeOwned,
|
||||||
|
{
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Nullable<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
// In order to deserialize a required, but nullable, value, we first have to check whether
|
||||||
|
// the value is present at all. To do this, we deserialize to a serde_json::Value, which
|
||||||
|
// fails if the value is missing, or gives serde_json::Value::Null if the value is present.
|
||||||
|
// If that succeeds as null, we can easily return a Null.
|
||||||
|
// If that succeeds as some value, we deserialize that value and return a Present.
|
||||||
|
// If that errors, we return the error.
|
||||||
|
let presence: Result<::serde_json::Value, _> =
|
||||||
|
serde::Deserialize::deserialize(deserializer);
|
||||||
|
match presence {
|
||||||
|
Ok(serde_json::Value::Null) => Ok(Nullable::Null),
|
||||||
|
Ok(some_value) => serde_json::from_value(some_value)
|
||||||
|
.map(Nullable::Present)
|
||||||
|
.map_err(serde::de::Error::custom),
|
||||||
|
Err(x) => Err(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> validator::Validate for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::Validate,
|
||||||
|
{
|
||||||
|
fn validate(&self) -> Result<(), validator::ValidationErrors> {
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.validate(),
|
||||||
|
Self::Null => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> validator::ValidateArgs<'a> for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateArgs<'a>,
|
||||||
|
{
|
||||||
|
type Args = T::Args;
|
||||||
|
fn validate_with_args(&self, args: Self::Args) -> Result<(), validator::ValidationErrors> {
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.validate_with_args(args),
|
||||||
|
Self::Null => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> validator::ValidateEmail for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateEmail,
|
||||||
|
{
|
||||||
|
fn as_email_string(&self) -> Option<std::borrow::Cow<str>> {
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.as_email_string(),
|
||||||
|
Self::Null => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> validator::ValidateUrl for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateUrl,
|
||||||
|
{
|
||||||
|
fn as_url_string(&self) -> Option<std::borrow::Cow<str>> {
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.as_url_string(),
|
||||||
|
Self::Null => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> validator::ValidateContains for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateContains,
|
||||||
|
{
|
||||||
|
fn validate_contains(&self, needle: &str) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.validate_contains(needle),
|
||||||
|
Self::Null => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> validator::ValidateRequired for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateRequired,
|
||||||
|
{
|
||||||
|
fn is_some(&self) -> bool {
|
||||||
|
self.is_present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> validator::ValidateRegex for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateRegex,
|
||||||
|
{
|
||||||
|
fn validate_regex(&self, regex: impl validator::AsRegex) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.validate_regex(regex),
|
||||||
|
Self::Null => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> validator::ValidateRange<I> for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateRange<I>,
|
||||||
|
{
|
||||||
|
fn greater_than(&self, max: I) -> Option<bool> {
|
||||||
|
use validator::ValidateRange;
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.greater_than(max),
|
||||||
|
Self::Null => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn less_than(&self, min: I) -> Option<bool> {
|
||||||
|
use validator::ValidateRange;
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.less_than(min),
|
||||||
|
Self::Null => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> validator::ValidateLength<I> for Nullable<T>
|
||||||
|
where
|
||||||
|
T: validator::ValidateLength<I>,
|
||||||
|
I: PartialEq + PartialOrd,
|
||||||
|
{
|
||||||
|
fn length(&self) -> Option<I> {
|
||||||
|
use validator::ValidateLength;
|
||||||
|
match self {
|
||||||
|
Self::Present(x) => x.length(),
|
||||||
|
Self::Null => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Nullable<T>> for Option<T> {
|
||||||
|
fn from(value: Nullable<T>) -> Option<T> {
|
||||||
|
match value {
|
||||||
|
Nullable::Present(x) => Some(x),
|
||||||
|
Nullable::Null => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
#[cold]
|
||||||
|
fn expect_failed(msg: &str) -> ! {
|
||||||
|
panic!("{}", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd)]
|
||||||
|
/// Base64-encoded byte array
|
||||||
|
pub struct ByteArray(pub Vec<u8>);
|
||||||
|
|
||||||
|
impl Serialize for ByteArray {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&general_purpose::STANDARD.encode(&self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for ByteArray {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<ByteArray, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
match general_purpose::STANDARD.decode(s) {
|
||||||
|
Ok(bin) => Ok(ByteArray(bin)),
|
||||||
|
_ => Err(serde::de::Error::custom("invalid base64")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,4 +7,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
@ -7,4 +7,5 @@ src/header.rs
|
|||||||
src/lib.rs
|
src/lib.rs
|
||||||
src/models.rs
|
src/models.rs
|
||||||
src/server/mod.rs
|
src/server/mod.rs
|
||||||
|
src/tests.rs
|
||||||
src/types.rs
|
src/types.rs
|
||||||
|
@ -26,3 +26,6 @@ pub mod types;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) mod header;
|
pub(crate) mod header;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn std_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tokio_test() {
|
||||||
|
assert!(true);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user