mangadex_api_schema_rust/
lib.rs

1#![deny(clippy::exhaustive_enums)]
2#![deny(clippy::exhaustive_structs)]
3
4//! MangaDex API response object types.
5
6mod bind;
7pub mod error;
8pub mod v5;
9
10use error::MangaDexErrorResponse_ as MangaDexErrorResponse;
11use mangadex_api_types::{RelationshipType, ResponseType, ResultType};
12use serde::de::DeserializeOwned;
13use serde::{Deserialize, Deserializer};
14use uuid::Uuid;
15
16use crate::v5::Relationship;
17
18#[derive(Deserialize)]
19#[serde(tag = "result", remote = "std::result::Result")]
20#[non_exhaustive]
21enum ApiResultDef<T, E> {
22    // this migth change in a near future
23    /// The server might often use `ko` in some 404 responses.
24    #[serde(rename = "ok", alias = "ko")]
25    Ok(T),
26    #[serde(rename = "error")]
27    Err(E),
28}
29
30#[derive(Deserialize)]
31#[serde(bound = "T: DeserializeOwned, E: DeserializeOwned")]
32pub struct ApiResult<T, E = MangaDexErrorResponse>(
33    #[serde(with = "ApiResultDef")] std::result::Result<T, E>,
34);
35
36impl<T, E> ApiResult<T, E> {
37    pub fn into_result(self) -> Result<T, E> {
38        self.0
39    }
40}
41
42/// API response for a single entity containing an [`ApiObject`] in the `data` field.
43#[derive(Debug, Deserialize, Clone)]
44#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
45#[cfg_attr(feature = "specta", derive(specta::Type))]
46#[non_exhaustive]
47pub struct ApiData<T> {
48    #[serde(default)]
49    pub result: ResultType,
50    pub response: ResponseType,
51    pub data: T,
52}
53
54impl<T> ApiData<T> {
55    fn new(data: T) -> ApiData<T> {
56        Self {
57            response: ResponseType::Entity,
58            data,
59            result: ResultType::Ok,
60        }
61    }
62}
63
64impl<T> Default for ApiData<T>
65where
66    T: Default,
67{
68    fn default() -> Self {
69        Self::new(T::default())
70    }
71}
72
73#[derive(Debug, Deserialize, Clone)]
74#[serde(rename_all = "camelCase")]
75#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
76#[cfg_attr(feature = "specta", derive(specta::Type))]
77#[non_exhaustive]
78pub struct ApiObject<A> {
79    pub id: Uuid,
80    pub type_: RelationshipType,
81    pub attributes: A,
82    pub relationships: Vec<Relationship>,
83}
84
85impl Default for ApiObject<()> {
86    fn default() -> Self {
87        Self {
88            id: Uuid::nil(),
89            type_: RelationshipType::Unknown,
90            attributes: (),
91            relationships: Vec::new(),
92        }
93    }
94}
95
96pub trait TypedAttributes {
97    const TYPE_: RelationshipType;
98}
99
100impl<A> Default for ApiObject<A>
101where
102    A: TypedAttributes + Default,
103{
104    fn default() -> Self {
105        Self {
106            id: Uuid::nil(),
107            type_: A::TYPE_,
108            attributes: A::default(),
109            relationships: Vec::new(),
110        }
111    }
112}
113
114impl<A> ApiObject<A> {
115    pub fn new(id: Uuid, type_: RelationshipType, attr: A) -> Self {
116        Self {
117            id,
118            type_,
119            attributes: attr,
120            relationships: Default::default(),
121        }
122    }
123    pub fn find_relationships(&self, type_: RelationshipType) -> Vec<&Relationship> {
124        self.relationships
125            .iter()
126            .filter(|rel| rel.type_ == type_)
127            .collect()
128    }
129    pub fn find_first_relationships(&self, type_: RelationshipType) -> Option<&Relationship> {
130        self.relationships.iter().find(|rel| rel.type_ == type_)
131    }
132}
133
134impl<T> From<ApiObject<T>> for ApiObjectNoRelationships<T> {
135    fn from(value: ApiObject<T>) -> Self {
136        Self {
137            id: value.id,
138            type_: value.type_,
139            attributes: value.attributes,
140        }
141    }
142}
143
144impl<T> ApiObject<T> {
145    pub fn drop_relationships(self) -> ApiObjectNoRelationships<T> {
146        self.into()
147    }
148}
149
150impl<T> ApiObjectNoRelationships<T> {
151    pub fn with_relathionships(self, rel: Option<Vec<Relationship>>) -> ApiObject<T> {
152        let mut res: ApiObject<T> = self.into();
153        let mut rels = rel.unwrap_or_default();
154        res.relationships.append(&mut rels);
155        res
156    }
157}
158
159impl<T> From<ApiObjectNoRelationships<T>> for ApiObject<T> {
160    fn from(value: ApiObjectNoRelationships<T>) -> Self {
161        Self {
162            id: value.id,
163            type_: value.type_,
164            attributes: value.attributes,
165            relationships: Vec::new(),
166        }
167    }
168}
169
170impl<T> PartialEq for ApiObject<T> {
171    fn eq(&self, other: &Self) -> bool {
172        self.id == other.id && self.type_ == other.type_
173    }
174}
175
176#[derive(Debug, Deserialize, Clone)]
177#[serde(rename_all = "camelCase")]
178#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
179#[cfg_attr(feature = "specta", derive(specta::Type))]
180#[non_exhaustive]
181pub struct ApiObjectNoRelationships<A> {
182    pub id: Uuid,
183    pub type_: RelationshipType,
184    pub attributes: A,
185}
186
187impl<A> Default for ApiObjectNoRelationships<A>
188where
189    A: TypedAttributes + Default,
190{
191    fn default() -> Self {
192        Self {
193            id: Uuid::nil(),
194            type_: A::TYPE_,
195            attributes: A::default(),
196        }
197    }
198}
199
200impl<A> ApiObjectNoRelationships<A> {
201    pub fn new(attributes: A) -> Self {
202        Self {
203            id: Uuid::nil(),
204            type_: RelationshipType::Unknown,
205            attributes,
206        }
207    }
208}
209
210/// Placeholder to hold response bodies that will be discarded.
211///
212/// `Result<()>` can't be used with the macro return type because it expects a unit type,
213/// so a temporary struct is used.
214///
215/// # Examples
216///
217/// ```text
218/// endpoint! {
219///     POST "/captcha/solve",
220///     #[body] SolveCaptcha<'_>,
221///     #[discard_result] Result<NoData> // `Result<()>` results in a deserialization error despite discarding the result.
222/// }
223#[derive(Debug, Default, Deserialize, Clone, Hash, PartialEq, Eq)]
224#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
225#[cfg_attr(feature = "specta", derive(specta::Type))]
226#[non_exhaustive]
227pub struct NoData {
228    #[serde(default)]
229    result: ResultType,
230}
231
232pub(crate) fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
233where
234    T: Default + Deserialize<'de>,
235    D: Deserializer<'de>,
236{
237    let opt = Option::deserialize(deserializer)?;
238    Ok(opt.unwrap_or_default())
239}
240
241/// There might be some edge cases where some endpoints returns that returns [`ApiObject::id`] that is not an [`Uuid`].
242///
243/// Currently, only the `GET /manga/{id}/recommendation` have that one.
244///
245#[derive(Debug, Deserialize, Clone)]
246#[serde(rename_all = "camelCase")]
247#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
248#[cfg_attr(feature = "specta", derive(specta::Type))]
249#[non_exhaustive]
250pub struct ApiObjectStringId<A> {
251    pub id: String,
252    pub type_: RelationshipType,
253    pub attributes: A,
254    pub relationships: Vec<Relationship>,
255}
256
257impl Default for ApiObjectStringId<()> {
258    fn default() -> Self {
259        Self {
260            id: Default::default(),
261            type_: RelationshipType::Unknown,
262            attributes: (),
263            relationships: Vec::new(),
264        }
265    }
266}
267
268impl<A> Default for ApiObjectStringId<A>
269where
270    A: TypedAttributes + Default,
271{
272    fn default() -> Self {
273        Self {
274            id: Default::default(),
275            type_: A::TYPE_,
276            attributes: A::default(),
277            relationships: Vec::new(),
278        }
279    }
280}
281
282impl<A> ApiObjectStringId<A> {
283    pub fn new(id: String, type_: RelationshipType, attr: A) -> Self {
284        Self {
285            id,
286            type_,
287            attributes: attr,
288            relationships: Default::default(),
289        }
290    }
291    pub fn find_relationships(&self, type_: RelationshipType) -> Vec<&Relationship> {
292        self.relationships
293            .iter()
294            .filter(|rel| rel.type_ == type_)
295            .collect()
296    }
297    pub fn find_first_relationships(&self, type_: RelationshipType) -> Option<&Relationship> {
298        self.relationships.iter().find(|rel| rel.type_ == type_)
299    }
300}
301
302impl<T> TryFrom<ApiObjectStringId<T>> for ApiObject<T> {
303    type Error = uuid::Error;
304    fn try_from(value: ApiObjectStringId<T>) -> Result<Self, Self::Error> {
305        Ok(Self {
306            id: value.id.parse()?,
307            type_: value.type_,
308            attributes: value.attributes,
309            relationships: value.relationships,
310        })
311    }
312}
313
314impl<T> From<ApiObject<T>> for ApiObjectStringId<T> {
315    fn from(value: ApiObject<T>) -> Self {
316        Self {
317            id: value.id.to_string(),
318            type_: value.type_,
319            attributes: value.attributes,
320            relationships: value.relationships,
321        }
322    }
323}
324
325/// An utility trait for finding relationships quickly from an object by their [RelationshipType].
326pub trait RelationedObject {
327    fn find_relationships(&self, type_: RelationshipType) -> Vec<&Relationship>;
328    fn find_first_relationships(&self, _type_: RelationshipType) -> Option<&Relationship> {
329        None
330    }
331}
332
333impl<T> RelationedObject for Vec<T>
334where
335    T: RelationedObject,
336{
337    fn find_first_relationships(&self, type_: RelationshipType) -> Option<&Relationship> {
338        self.iter().find_map(|d| d.find_first_relationships(type_))
339    }
340    fn find_relationships(&self, type_: RelationshipType) -> Vec<&Relationship> {
341        self.iter()
342            .flat_map(|d| d.find_relationships(type_))
343            .collect()
344    }
345}
346
347impl<T> RelationedObject for v5::Results<T>
348where
349    T: RelationedObject,
350{
351    fn find_relationships(&self, type_: RelationshipType) -> Vec<&Relationship> {
352        self.data.find_relationships(type_)
353    }
354    fn find_first_relationships(&self, type_: RelationshipType) -> Option<&Relationship> {
355        self.data.find_first_relationships(type_)
356    }
357}
358
359macro_rules! impl_api_obj {
360    ($type:ty) => {
361        impl<T> RelationedObject for $type {
362            fn find_relationships(&self, type_: RelationshipType) -> Vec<&Relationship> {
363                self.relationships
364                    .iter()
365                    .filter(|rel| rel.type_ == type_)
366                    .collect()
367            }
368            fn find_first_relationships(&self, type_: RelationshipType) -> Option<&Relationship> {
369                self.relationships.iter().find(|rel| rel.type_ == type_)
370            }
371        }
372    };
373}
374
375impl_api_obj!(ApiObject<T>);
376impl_api_obj!(ApiObjectStringId<T>);