1use serde::{Deserialize, Serialize};
4
5use crate::model::extensions::Extensions;
6
7#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
9pub struct Info {
10 pub title: String,
12
13 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub summary: Option<String>,
16
17 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub description: Option<String>,
22
23 #[serde(
25 rename = "termsOfService",
26 default,
27 skip_serializing_if = "Option::is_none"
28 )]
29 pub terms_of_service: Option<String>,
30
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub contact: Option<Contact>,
34
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub license: Option<License>,
38
39 pub version: String,
45
46 #[serde(flatten)]
48 pub extensions: Extensions,
49}
50
51impl Info {
52 pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
54 Self {
55 title: title.into(),
56 version: version.into(),
57 ..Self::default()
58 }
59 }
60
61 #[must_use]
63 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
64 self.summary = Some(summary.into());
65 self
66 }
67
68 #[must_use]
70 pub fn with_description(mut self, description: impl Into<String>) -> Self {
71 self.description = Some(description.into());
72 self
73 }
74
75 #[must_use]
77 pub fn with_contact(mut self, contact: Contact) -> Self {
78 self.contact = Some(contact);
79 self
80 }
81
82 #[must_use]
84 pub fn with_license(mut self, license: License) -> Self {
85 self.license = Some(license);
86 self
87 }
88}
89
90#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
92pub struct Contact {
93 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub name: Option<String>,
96
97 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub url: Option<String>,
100
101 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub email: Option<String>,
104
105 #[serde(flatten)]
107 pub extensions: Extensions,
108}
109
110#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
122#[serde(try_from = "RawLicense", into = "RawLicense")]
123pub struct License {
124 name: String,
125 link: Option<LicenseLink>,
126
127 pub extensions: Extensions,
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
137enum LicenseLink {
138 Spdx(String),
142
143 Url(String),
145}
146
147impl License {
148 pub fn named(name: impl Into<String>) -> Self {
153 Self {
154 name: name.into(),
155 link: None,
156 extensions: Extensions::default(),
157 }
158 }
159
160 pub fn spdx(name: impl Into<String>, identifier: impl Into<String>) -> Self {
165 Self {
166 link: Some(LicenseLink::Spdx(identifier.into())),
167 ..Self::named(name)
168 }
169 }
170
171 pub fn with_url(name: impl Into<String>, url: impl Into<String>) -> Self {
173 Self {
174 link: Some(LicenseLink::Url(url.into())),
175 ..Self::named(name)
176 }
177 }
178
179 #[must_use]
181 pub fn name(&self) -> &str {
182 &self.name
183 }
184
185 #[must_use]
189 pub fn identifier(&self) -> Option<&str> {
190 match &self.link {
191 Some(LicenseLink::Spdx(identifier)) => Some(identifier),
192 _ => None,
193 }
194 }
195
196 #[must_use]
198 pub fn url(&self) -> Option<&str> {
199 match &self.link {
200 Some(LicenseLink::Url(url)) => Some(url),
201 _ => None,
202 }
203 }
204}
205
206#[derive(Serialize, Deserialize)]
212struct RawLicense {
213 name: String,
214
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 identifier: Option<String>,
217
218 #[serde(default, skip_serializing_if = "Option::is_none")]
219 url: Option<String>,
220
221 #[serde(flatten)]
222 extensions: Extensions,
223}
224
225#[derive(Debug)]
227struct LicenseConflict;
228
229impl std::fmt::Display for LicenseConflict {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 f.write_str("`identifier` and `url` are mutually exclusive on a License Object")
232 }
233}
234
235impl TryFrom<RawLicense> for License {
236 type Error = LicenseConflict;
237
238 fn try_from(raw: RawLicense) -> Result<Self, Self::Error> {
239 let link = match (raw.identifier, raw.url) {
240 (Some(_), Some(_)) => return Err(LicenseConflict),
241 (Some(identifier), None) => Some(LicenseLink::Spdx(identifier)),
242 (None, Some(url)) => Some(LicenseLink::Url(url)),
243 (None, None) => None,
244 };
245
246 Ok(Self {
247 name: raw.name,
248 link,
249 extensions: raw.extensions,
250 })
251 }
252}
253
254impl From<License> for RawLicense {
255 fn from(license: License) -> Self {
256 let (identifier, url) = match license.link {
257 Some(LicenseLink::Spdx(identifier)) => (Some(identifier), None),
258 Some(LicenseLink::Url(url)) => (None, Some(url)),
259 None => (None, None),
260 };
261
262 Self {
263 name: license.name,
264 identifier,
265 url,
266 extensions: license.extensions,
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests;