mangadex_api/v5/manga/id/aggregate/
get.rs

1//! Builder for the manga aggregate endpoint to get volumes and chapters.
2//!
3//! <https://api.mangadex.org/docs/swagger.html#/Manga/get_manga__id__aggregate>
4//!
5//! # Examples
6//!
7//! ```rust
8//! use uuid::Uuid;
9//!
10//! use mangadex_api::v5::MangaDexClient;
11//!
12//! # async fn run() -> anyhow::Result<()> {
13//! let client = MangaDexClient::default();
14//!
15//! let manga_id = Uuid::new_v4();
16//! let manga_res = client
17//!     .manga()
18//!     .id(manga_id)
19//!     .aggregate()
20//!     .get()
21//!     .send()
22//!     .await?;
23//!
24//! println!("manga aggregate: {:?}", manga_res);
25//! # Ok(())
26//! # }
27//! ```
28
29use derive_builder::Builder;
30use serde::Serialize;
31use uuid::Uuid;
32
33use crate::HttpClientRef;
34use mangadex_api_schema::v5::MangaAggregateResponse;
35use mangadex_api_types::Language;
36
37#[cfg_attr(
38    feature = "deserializable-endpoint",
39    derive(serde::Deserialize, getset::Getters, getset::Setters)
40)]
41#[derive(Debug, Serialize, Clone, Builder)]
42#[serde(rename_all = "camelCase")]
43#[builder(
44    setter(into, strip_option),
45    build_fn(error = "mangadex_api_types::error::BuilderError")
46)]
47#[cfg_attr(feature = "non_exhaustive", non_exhaustive)]
48pub struct GetMangaAggregate {
49    /// This should never be set manually as this is only for internal use.
50    #[doc(hidden)]
51    #[serde(skip)]
52    #[builder(pattern = "immutable")]
53    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
54    pub http_client: HttpClientRef,
55
56    #[serde(skip_serializing)]
57    pub manga_id: Uuid,
58
59    #[builder(setter(each = "add_language"), default)]
60    pub translated_language: Vec<Language>,
61    #[builder(setter(each = "add_group"), default)]
62    pub groups: Vec<Uuid>,
63}
64
65endpoint! {
66    GET ("/manga/{}/aggregate", manga_id),
67    #[query] GetMangaAggregate,
68    #[flatten_result] MangaAggregateResponse,
69    GetMangaAggregateBuilder
70}
71
72#[cfg(test)]
73mod tests {
74    use serde_json::json;
75    use url::Url;
76    use uuid::Uuid;
77    use wiremock::matchers::{method, path_regex};
78    use wiremock::{Mock, MockServer, ResponseTemplate};
79
80    use crate::{HttpClient, MangaDexClient};
81
82    #[tokio::test]
83    async fn manga_aggregate_fires_a_request_to_base_url() -> anyhow::Result<()> {
84        let mock_server = MockServer::start().await;
85        let http_client: HttpClient = HttpClient::builder()
86            .base_url(Url::parse(&mock_server.uri())?)
87            .build()?;
88        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
89
90        let manga_id = Uuid::new_v4();
91        let chapter_id = Uuid::new_v4();
92        let response_body = json!({
93            "result": "ok",
94            "volumes": {
95                "1": {
96                    "volume": "1",
97                    "count": 2,
98                    "chapters": {
99                        "26": {
100                            "chapter": "26",
101                            "id": chapter_id,
102                            "others": [],
103                            "count": 2
104                        }
105                    }
106                }
107            }
108        });
109
110        Mock::given(method("GET"))
111            .and(path_regex(r"/manga/[0-9a-fA-F-]+/aggregate"))
112            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
113            .expect(1)
114            .mount(&mock_server)
115            .await;
116
117        let res = mangadex_client
118            .manga()
119            .id(manga_id)
120            .aggregate()
121            .get()
122            .send()
123            .await?;
124
125        assert_eq!(res.volumes.len(), 1);
126
127        let volume = &res.volumes[0];
128        assert_eq!(volume.volume, "1");
129        assert_eq!(volume.count, 2);
130
131        assert_eq!(volume.chapters.len(), 1);
132        let chapter = &volume.chapters[0];
133        assert_eq!(chapter.chapter, "26");
134        assert_eq!(chapter.id, chapter_id);
135        assert_eq!(chapter.count, 2);
136
137        Ok(())
138    }
139
140    #[tokio::test]
141    async fn manga_aggregate_handles_array_volumes() -> anyhow::Result<()> {
142        let mock_server = MockServer::start().await;
143        let http_client: HttpClient = HttpClient::builder()
144            .base_url(Url::parse(&mock_server.uri())?)
145            .build()?;
146        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
147
148        let manga_id = Uuid::new_v4();
149        let response_body = json!({
150            "result": "ok",
151            "volumes": []
152        });
153
154        Mock::given(method("GET"))
155            .and(path_regex(r"/manga/[0-9a-fA-F-]+/aggregate"))
156            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
157            .expect(1)
158            .mount(&mock_server)
159            .await;
160
161        let res = mangadex_client
162            .manga()
163            .id(manga_id)
164            .aggregate()
165            .get()
166            .send()
167            .await?;
168
169        assert_eq!(res.volumes.len(), 0);
170
171        Ok(())
172    }
173
174    #[tokio::test]
175    async fn manga_aggregate_handles_array_chapters() -> anyhow::Result<()> {
176        let mock_server = MockServer::start().await;
177        let http_client: HttpClient = HttpClient::builder()
178            .base_url(Url::parse(&mock_server.uri())?)
179            .build()?;
180        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
181
182        let manga_id = Uuid::new_v4();
183        let chapter_id = Uuid::parse_str("01defbd2-ab44-4672-9236-ff71b82774e8").unwrap();
184        let response_body = json!({
185            "result": "ok",
186            "volumes": {
187                "0": {
188                    "volume": "0",
189                    "count": 1,
190                    "chapters": [
191                        {
192                            "chapter": "0",
193                            "id": "01defbd2-ab44-4672-9236-ff71b82774e8",
194                            "others": [],
195                            "count": 1
196                        }
197                    ]
198                },
199                "1": {
200                    "volume": "1",
201                    "count": 9,
202                    "chapters": {
203                        "1": {
204                            "chapter": "1",
205                            "id": "7729dc89-38bd-4e90-a566-e63558d28cfd",
206                            "others": [
207                                "c30e8966-cef8-46c6-bc31-f24f6827bf84",
208                                "b5ced190-b89f-43e9-b915-7fc0e5596c71"
209                            ],
210                            "count": 3
211                        },
212                        "2": {
213                            "chapter": "2",
214                            "id": "68f1bce2-1ea3-4100-b8a5-3c182507906c",
215                            "others": [
216                                "6fd96344-741d-4a6c-9ff0-0a177f459cbc",
217                                "b1ae77a4-d587-4fd7-8f32-d180c619d9bf"
218                            ],
219                            "count": 3
220                        },
221                        "3": {
222                            "chapter": "3",
223                            "id": "8ea939ec-02fb-4a4a-836c-ae09871c3354",
224                            "others": [
225                                "cbe758e6-ea2e-4cb4-a621-df9e06181519",
226                                "f08c1906-b2a6-45fa-a26a-4cc3b1cf1dab"
227                            ],
228                            "count": 3
229                        }
230                    }
231                },
232            }
233        });
234
235        Mock::given(method("GET"))
236            .and(path_regex(r"/manga/[0-9a-fA-F-]+/aggregate"))
237            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
238            .expect(1)
239            .mount(&mock_server)
240            .await;
241
242        let res = mangadex_client
243            .manga()
244            .id(manga_id)
245            .aggregate()
246            .get()
247            .send()
248            .await?;
249
250        assert_eq!(res.volumes.len(), 2);
251
252        let volume = &res.volumes[0];
253        assert_eq!(volume.volume, "0");
254        assert_eq!(volume.count, 1);
255        assert_eq!(volume.chapters.len(), 1);
256        let chapter = &volume.chapters[0];
257        assert_eq!(chapter.chapter, "0");
258        assert_eq!(chapter.id, chapter_id);
259        assert_eq!(chapter.count, 1);
260
261        Ok(())
262    }
263}