1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::{
9 Map,
10 model::{extensions::Extensions, server::Server},
11};
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(try_from = "RawLink", into = "RawLink")]
23pub struct Link {
24 target: LinkTarget,
25
26 pub parameters: Map<Value>,
31
32 pub request_body: Option<Value>,
34
35 pub description: Option<String>,
39
40 pub server: Option<Server>,
42
43 pub extensions: Extensions,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum LinkTarget {
56 Uri(String),
62
63 Id(String),
66}
67
68impl Link {
69 pub fn to_operation(operation_id: impl Into<String>) -> Self {
71 Self::targeting(LinkTarget::Id(operation_id.into()))
72 }
73
74 pub fn to_operation_ref(operation_ref: impl Into<String>) -> Self {
76 Self::targeting(LinkTarget::Uri(operation_ref.into()))
77 }
78
79 fn targeting(target: LinkTarget) -> Self {
80 Self {
81 target,
82 parameters: Map::new(),
83 request_body: None,
84 description: None,
85 server: None,
86 extensions: Extensions::default(),
87 }
88 }
89
90 #[must_use]
92 pub fn target(&self) -> &LinkTarget {
93 &self.target
94 }
95
96 #[must_use]
98 pub fn operation_ref(&self) -> Option<&str> {
99 match &self.target {
100 LinkTarget::Uri(uri) => Some(uri),
101 LinkTarget::Id(_) => None,
102 }
103 }
104
105 #[must_use]
107 pub fn operation_id(&self) -> Option<&str> {
108 match &self.target {
109 LinkTarget::Id(id) => Some(id),
110 LinkTarget::Uri(_) => None,
111 }
112 }
113
114 #[must_use]
116 pub fn with_parameter(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
117 self.parameters.insert(name.into(), value.into());
118 self
119 }
120
121 #[must_use]
123 pub fn with_description(mut self, description: impl Into<String>) -> Self {
124 self.description = Some(description.into());
125 self
126 }
127}
128
129#[derive(Serialize, Deserialize)]
131struct RawLink {
132 #[serde(
133 rename = "operationRef",
134 default,
135 skip_serializing_if = "Option::is_none"
136 )]
137 operation_ref: Option<String>,
138
139 #[serde(
140 rename = "operationId",
141 default,
142 skip_serializing_if = "Option::is_none"
143 )]
144 operation_id: Option<String>,
145
146 #[serde(default, skip_serializing_if = "Map::is_empty")]
147 parameters: Map<Value>,
148
149 #[serde(
150 rename = "requestBody",
151 default,
152 deserialize_with = "crate::model::nullable::some",
153 skip_serializing_if = "Option::is_none"
154 )]
155 request_body: Option<Value>,
156
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 description: Option<String>,
159
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 server: Option<Server>,
162
163 #[serde(flatten)]
164 extensions: Extensions,
165}
166
167#[derive(Debug)]
169enum LinkConflict {
170 Neither,
172
173 Both,
175}
176
177impl fmt::Display for LinkConflict {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 match self {
180 Self::Neither => {
181 f.write_str("one of `operationRef` and `operationId` is required on a Link Object")
182 }
183 Self::Both => f.write_str(
184 "`operationRef` and `operationId` are mutually exclusive on a Link Object",
185 ),
186 }
187 }
188}
189
190impl TryFrom<RawLink> for Link {
191 type Error = LinkConflict;
192
193 fn try_from(raw: RawLink) -> Result<Self, Self::Error> {
194 let target = match (raw.operation_ref, raw.operation_id) {
195 (Some(_), Some(_)) => return Err(LinkConflict::Both),
196 (Some(uri), None) => LinkTarget::Uri(uri),
197 (None, Some(id)) => LinkTarget::Id(id),
198 (None, None) => return Err(LinkConflict::Neither),
199 };
200
201 Ok(Self {
202 target,
203 parameters: raw.parameters,
204 request_body: raw.request_body,
205 description: raw.description,
206 server: raw.server,
207 extensions: raw.extensions,
208 })
209 }
210}
211
212impl From<Link> for RawLink {
213 fn from(link: Link) -> Self {
214 let (operation_ref, operation_id) = match link.target {
215 LinkTarget::Uri(uri) => (Some(uri), None),
216 LinkTarget::Id(id) => (None, Some(id)),
217 };
218
219 Self {
220 operation_ref,
221 operation_id,
222 parameters: link.parameters,
223 request_body: link.request_body,
224 description: link.description,
225 server: link.server,
226 extensions: link.extensions,
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests;