use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
Map,
model::{extensions::Extensions, reference::RefOr},
};
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "RawExample", into = "RawExample")]
pub struct Example {
pub summary: Option<String>,
pub description: Option<String>,
value: Option<ExampleValue>,
pub extensions: Extensions,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExampleValue {
Embedded(Value),
#[non_exhaustive]
External {
uri: String,
#[cfg(feature = "openapi32")]
data: Option<Value>,
},
#[cfg(feature = "openapi32")]
Data {
data: Value,
serialized: Option<String>,
},
#[cfg(feature = "openapi32")]
Serialized(String),
}
impl Example {
pub fn new(value: impl Into<Value>) -> Self {
Self::carrying(ExampleValue::Embedded(value.into()))
}
pub fn external(uri: impl Into<String>) -> Self {
Self::carrying(ExampleValue::External {
uri: uri.into(),
#[cfg(feature = "openapi32")]
data: None,
})
}
#[cfg(feature = "openapi32")]
pub fn data(data: impl Into<Value>) -> Self {
Self::carrying(ExampleValue::Data {
data: data.into(),
serialized: None,
})
}
#[cfg(feature = "openapi32")]
pub fn serialized(serialized: impl Into<String>) -> Self {
Self::carrying(ExampleValue::Serialized(serialized.into()))
}
#[cfg(feature = "openapi32")]
pub fn data_serialized(data: impl Into<Value>, serialized: impl Into<String>) -> Self {
Self::carrying(ExampleValue::Data {
data: data.into(),
serialized: Some(serialized.into()),
})
}
#[cfg(feature = "openapi32")]
pub fn data_external(data: impl Into<Value>, uri: impl Into<String>) -> Self {
Self::carrying(ExampleValue::External {
uri: uri.into(),
data: Some(data.into()),
})
}
#[must_use]
pub fn value(&self) -> Option<&ExampleValue> {
self.value.as_ref()
}
fn carrying(value: ExampleValue) -> Self {
Self {
value: Some(value),
..Self::default()
}
}
#[must_use]
pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
self.summary = Some(summary.into());
self
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
#[derive(Serialize, Deserialize)]
struct RawExample {
#[serde(default, skip_serializing_if = "Option::is_none")]
summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(
default,
deserialize_with = "crate::model::nullable::some",
skip_serializing_if = "Option::is_none"
)]
value: Option<Value>,
#[cfg(feature = "openapi32")]
#[serde(
rename = "dataValue",
default,
deserialize_with = "crate::model::nullable::some",
skip_serializing_if = "Option::is_none"
)]
data_value: Option<Value>,
#[cfg(feature = "openapi32")]
#[serde(
rename = "serializedValue",
default,
skip_serializing_if = "Option::is_none"
)]
serialized_value: Option<String>,
#[serde(
rename = "externalValue",
default,
skip_serializing_if = "Option::is_none"
)]
external_value: Option<String>,
#[serde(flatten)]
extensions: Extensions,
}
#[derive(Debug)]
enum ExampleConflict {
ValueNotAlone,
#[cfg(feature = "openapi32")]
TwoSerializations,
}
impl std::fmt::Display for ExampleConflict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ValueNotAlone => f.write_str(
"`value` is mutually exclusive with `dataValue`, `serializedValue` and \
`externalValue` on an Example Object",
),
#[cfg(feature = "openapi32")]
Self::TwoSerializations => f.write_str(
"`serializedValue` and `externalValue` are mutually exclusive on an Example \
Object",
),
}
}
}
impl TryFrom<RawExample> for Example {
type Error = ExampleConflict;
fn try_from(raw: RawExample) -> Result<Self, Self::Error> {
#[cfg(feature = "openapi32")]
let value = match (
raw.value,
raw.data_value,
raw.serialized_value,
raw.external_value,
) {
(Some(_), Some(_), _, _) | (Some(_), _, Some(_), _) | (Some(_), _, _, Some(_)) => {
return Err(ExampleConflict::ValueNotAlone);
}
(_, _, Some(_), Some(_)) => return Err(ExampleConflict::TwoSerializations),
(Some(value), None, None, None) => Some(ExampleValue::Embedded(value)),
(None, data, None, Some(uri)) => Some(ExampleValue::External { uri, data }),
(None, Some(data), serialized, None) => Some(ExampleValue::Data { data, serialized }),
(None, None, Some(serialized), None) => Some(ExampleValue::Serialized(serialized)),
(None, None, None, None) => None,
};
#[cfg(not(feature = "openapi32"))]
let value = match (raw.value, raw.external_value) {
(Some(_), Some(_)) => return Err(ExampleConflict::ValueNotAlone),
(Some(value), None) => Some(ExampleValue::Embedded(value)),
(None, Some(uri)) => Some(ExampleValue::External { uri }),
(None, None) => None,
};
Ok(Self {
summary: raw.summary,
description: raw.description,
value,
extensions: raw.extensions,
})
}
}
impl From<Example> for RawExample {
fn from(example: Example) -> Self {
#[cfg(feature = "openapi32")]
let (value, data_value, serialized_value, external_value) = match example.value {
Some(ExampleValue::Embedded(value)) => (Some(value), None, None, None),
Some(ExampleValue::External { uri, data }) => (None, data, None, Some(uri)),
Some(ExampleValue::Data { data, serialized }) => (None, Some(data), serialized, None),
Some(ExampleValue::Serialized(serialized)) => (None, None, Some(serialized), None),
None => (None, None, None, None),
};
#[cfg(not(feature = "openapi32"))]
let (value, external_value) = match example.value {
Some(ExampleValue::Embedded(value)) => (Some(value), None),
Some(ExampleValue::External { uri }) => (None, Some(uri)),
None => (None, None),
};
Self {
summary: example.summary,
description: example.description,
value,
#[cfg(feature = "openapi32")]
data_value,
#[cfg(feature = "openapi32")]
serialized_value,
external_value,
extensions: example.extensions,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Examples {
Inline(Value),
Named(Map<RefOr<Example>>),
}
#[derive(Debug)]
pub(crate) struct ExamplesConflict;
impl std::fmt::Display for ExamplesConflict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("`example` and `examples` are mutually exclusive")
}
}
pub(crate) fn examples_from(
example: Option<Value>,
examples: Option<Map<RefOr<Example>>>,
) -> Result<Option<Examples>, ExamplesConflict> {
match (example, examples) {
(Some(_), Some(_)) => Err(ExamplesConflict),
(Some(value), None) => Ok(Some(Examples::Inline(value))),
(None, Some(named)) => Ok(Some(Examples::Named(named))),
(None, None) => Ok(None),
}
}
pub(crate) fn examples_into(
examples: Option<Examples>,
) -> (Option<Value>, Option<Map<RefOr<Example>>>) {
match examples {
Some(Examples::Inline(value)) => (Some(value), None),
Some(Examples::Named(named)) => (None, Some(named)),
None => (None, None),
}
}
pub(crate) fn examples_with_named(
examples: Option<Examples>,
name: String,
example: RefOr<Example>,
) -> Examples {
let mut named = match examples {
Some(Examples::Named(named)) => named,
Some(Examples::Inline(_)) | None => Map::new(),
};
named.insert(name, example);
Examples::Named(named)
}
#[cfg(test)]
mod tests;