use std::fmt;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
Map,
model::{extensions::Extensions, server::Server},
};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "RawLink", into = "RawLink")]
pub struct Link {
target: LinkTarget,
pub parameters: Map<Value>,
pub request_body: Option<Value>,
pub description: Option<String>,
pub server: Option<Server>,
pub extensions: Extensions,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LinkTarget {
Uri(String),
Id(String),
}
impl Link {
pub fn to_operation(operation_id: impl Into<String>) -> Self {
Self::targeting(LinkTarget::Id(operation_id.into()))
}
pub fn to_operation_ref(operation_ref: impl Into<String>) -> Self {
Self::targeting(LinkTarget::Uri(operation_ref.into()))
}
fn targeting(target: LinkTarget) -> Self {
Self {
target,
parameters: Map::new(),
request_body: None,
description: None,
server: None,
extensions: Extensions::default(),
}
}
#[must_use]
pub fn target(&self) -> &LinkTarget {
&self.target
}
#[must_use]
pub fn operation_ref(&self) -> Option<&str> {
match &self.target {
LinkTarget::Uri(uri) => Some(uri),
LinkTarget::Id(_) => None,
}
}
#[must_use]
pub fn operation_id(&self) -> Option<&str> {
match &self.target {
LinkTarget::Id(id) => Some(id),
LinkTarget::Uri(_) => None,
}
}
#[must_use]
pub fn with_parameter(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
self.parameters.insert(name.into(), value.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 RawLink {
#[serde(
rename = "operationRef",
default,
skip_serializing_if = "Option::is_none"
)]
operation_ref: Option<String>,
#[serde(
rename = "operationId",
default,
skip_serializing_if = "Option::is_none"
)]
operation_id: Option<String>,
#[serde(default, skip_serializing_if = "Map::is_empty")]
parameters: Map<Value>,
#[serde(
rename = "requestBody",
default,
deserialize_with = "crate::model::nullable::some",
skip_serializing_if = "Option::is_none"
)]
request_body: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
server: Option<Server>,
#[serde(flatten)]
extensions: Extensions,
}
#[derive(Debug)]
enum LinkConflict {
Neither,
Both,
}
impl fmt::Display for LinkConflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Neither => {
f.write_str("one of `operationRef` and `operationId` is required on a Link Object")
}
Self::Both => f.write_str(
"`operationRef` and `operationId` are mutually exclusive on a Link Object",
),
}
}
}
impl TryFrom<RawLink> for Link {
type Error = LinkConflict;
fn try_from(raw: RawLink) -> Result<Self, Self::Error> {
let target = match (raw.operation_ref, raw.operation_id) {
(Some(_), Some(_)) => return Err(LinkConflict::Both),
(Some(uri), None) => LinkTarget::Uri(uri),
(None, Some(id)) => LinkTarget::Id(id),
(None, None) => return Err(LinkConflict::Neither),
};
Ok(Self {
target,
parameters: raw.parameters,
request_body: raw.request_body,
description: raw.description,
server: raw.server,
extensions: raw.extensions,
})
}
}
impl From<Link> for RawLink {
fn from(link: Link) -> Self {
let (operation_ref, operation_id) = match link.target {
LinkTarget::Uri(uri) => (Some(uri), None),
LinkTarget::Id(id) => (None, Some(id)),
};
Self {
operation_ref,
operation_id,
parameters: link.parameters,
request_body: link.request_body,
description: link.description,
server: link.server,
extensions: link.extensions,
}
}
}
#[cfg(test)]
mod tests;