mangadex_api/v5/manga/id/aggregate/
get.rs1use 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 #[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}