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

1//! Builder for getting a specific Manga Draft.
2//!
3//! This endpoint requires authentication.
4//!
5//! This endpoint is largely identical to the Manga Get endpoint except that this fetches Manga
6//! that is not in the "published" state.
7//!
8//! <https://api.mangadex.org/docs/swagger.html#/Manga/get-manga-id-draft>
9//!
10//! # Examples
11//!
12//! ```rust
13//! use uuid::Uuid;
14//!
15//! use mangadex_api::MangaDexClient;
16//! use mangadex_api_types::{Password, Username};
17//!
18//! # async fn run() -> anyhow::Result<()> {
19//! let client = MangaDexClient::default();
20//!
21//! /*
22//!     // Put your login script here
23//!     let _login_res = client
24//!         .auth()
25//!         .login()
26//!         .post()
27//!         .username(Username::parse("myusername")?)
28//!         .password(Password::parse("hunter23")?)
29//!         .send()
30//!         .await?;
31//!  */
32//!
33//! let manga_id = Uuid::new_v4();
34//! let manga_res = client
35//!     .manga()
36//!     .draft()
37//!     .id(manga_id)
38//!     .get()
39//!     .send()
40//!     .await?;
41//!
42//! println!("manga draft view: {:?}", manga_res);
43//! # Ok(())
44//! # }
45//! ```
46
47use derive_builder::Builder;
48use serde::Serialize;
49use uuid::Uuid;
50
51use crate::HttpClientRef;
52use mangadex_api_schema::v5::MangaResponse;
53use mangadex_api_types::ReferenceExpansionResource;
54
55#[cfg_attr(
56    feature = "deserializable-endpoint",
57    derive(serde::Deserialize, getset::Getters, getset::Setters)
58)]
59#[derive(Debug, Serialize, Clone, Builder)]
60#[serde(rename_all = "camelCase")]
61#[builder(
62    setter(into, strip_option),
63    build_fn(error = "mangadex_api_types::error::BuilderError")
64)]
65pub struct GetMangaDraft {
66    /// This should never be set manually as this is only for internal use.
67    #[doc(hidden)]
68    #[serde(skip)]
69    #[builder(pattern = "immutable")]
70    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
71    pub http_client: HttpClientRef,
72
73    #[serde(skip_serializing)]
74    pub manga_id: Uuid,
75
76    #[builder(setter(each = "include"), default)]
77    pub includes: Vec<ReferenceExpansionResource>,
78}
79
80endpoint! {
81    GET ("/manga/draft/{}", manga_id),
82    #[query auth] GetMangaDraft,
83    #[flatten_result] MangaResponse,
84    GetMangaDraftBuilder
85}
86
87#[cfg(test)]
88mod tests {
89    use serde_json::json;
90    use time::OffsetDateTime;
91    use url::Url;
92    use uuid::Uuid;
93    use wiremock::matchers::{header, method, path_regex};
94    use wiremock::{Mock, MockServer, ResponseTemplate};
95
96    use crate::v5::AuthTokens;
97    use crate::{HttpClient, MangaDexClient};
98    use mangadex_api_schema::v5::RelatedAttributes;
99    use mangadex_api_types::error::Error;
100    use mangadex_api_types::{
101        MangaDexDateTime, MangaRelation, ReferenceExpansionResource, RelationshipType,
102    };
103
104    #[tokio::test]
105    async fn get_manga_draft_fires_a_request_to_base_url() -> anyhow::Result<()> {
106        let mock_server = MockServer::start().await;
107        let http_client: HttpClient = HttpClient::builder()
108            .base_url(Url::parse(&mock_server.uri())?)
109            .auth_tokens(AuthTokens {
110                session: "sessiontoken".to_string(),
111                refresh: "refreshtoken".to_string(),
112            })
113            .build()?;
114        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
115
116        let manga_id = Uuid::new_v4();
117
118        let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
119
120        let response_body = json!({
121            "result": "ok",
122            "response": "entity",
123            "data": {
124                "id": manga_id,
125                "type": "manga",
126                "attributes": {
127                    "title": {
128                        "en": "Test Manga"
129                    },
130                    "altTitles": [],
131                    "description": {},
132                    "isLocked": false,
133                    "links": {},
134                    "originalLanguage": "ja",
135                    "lastVolume": "1",
136                    "lastChapter": "1",
137                    "publicationDemographic": "shoujo",
138                    "status": "completed",
139                    "year": 2021,
140                    "contentRating": "safe",
141                    "chapterNumbersResetOnNewVolume": true,
142                    "availableTranslatedLanguages": ["en"],
143                    "tags": [],
144                    "state": "rejected",
145                    "version": 1,
146                    "createdAt": datetime.to_string(),
147                    "updatedAt": datetime.to_string(),
148                },
149                "relationships": [
150                    {
151                        "id": "a3219a4f-73c0-4213-8730-05985130539a",
152                        "type": "manga",
153                        "related": "side_story",
154                    }
155                ]
156            }
157        });
158
159        Mock::given(method("GET"))
160            .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+"))
161            .and(header("Authorization", "Bearer sessiontoken"))
162            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
163            .expect(1)
164            .mount(&mock_server)
165            .await;
166
167        let res = mangadex_client
168            .manga()
169            .draft()
170            .id(manga_id)
171            .get()
172            .build()?
173            .send()
174            .await?;
175
176        assert_eq!(res.data.relationships[0].type_, RelationshipType::Manga);
177        assert_eq!(
178            res.data.relationships[0].related,
179            Some(MangaRelation::SideStory)
180        );
181        assert!(res.data.relationships[0].attributes.is_none());
182
183        Ok(())
184    }
185
186    #[tokio::test]
187    async fn get_manga_draft_handles_reference_expansion() -> anyhow::Result<()> {
188        let mock_server = MockServer::start().await;
189        let http_client: HttpClient = HttpClient::builder()
190            .base_url(Url::parse(&mock_server.uri())?)
191            .auth_tokens(AuthTokens {
192                session: "sessiontoken".to_string(),
193                refresh: "refreshtoken".to_string(),
194            })
195            .build()?;
196        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
197
198        let manga_id = Uuid::new_v4();
199
200        let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
201
202        let response_body = json!({
203            "result": "ok",
204            "response": "entity",
205            "data": {
206                "id": manga_id,
207                "type": "manga",
208                "attributes": {
209                    "title": {
210                        "en": "Test Manga"
211                    },
212                    "altTitles": [],
213                    "description": {},
214                    "isLocked": false,
215                    "links": {},
216                    "originalLanguage": "ja",
217                    "lastVolume": "1",
218                    "lastChapter": "1",
219                    "publicationDemographic": "shoujo",
220                    "status": "completed",
221                    "year": 2021,
222                    "contentRating": "safe",
223                    "chapterNumbersResetOnNewVolume": true,
224                    "availableTranslatedLanguages": ["en"],
225                    "tags": [],
226                    "state": "submitted",
227                    "version": 1,
228                    "createdAt": datetime.to_string(),
229                    "updatedAt": datetime.to_string(),
230                },
231                "relationships": [
232                    {
233                        "id": "fc343004-569b-4750-aba0-05ab35efc17c",
234                        "type": "author",
235                        "attributes": {
236                            "name": "Hologfx",
237                            "imageUrl": null,
238                            "biography": [],
239                            "twitter": null,
240                            "pixiv": null,
241                            "melonBook": null,
242                            "fanBox": null,
243                            "booth": null,
244                            "nicoVideo": null,
245                            "skeb": null,
246                            "fantia": null,
247                            "tumblr": null,
248                            "youtube": null,
249                            "website": null,
250                            "createdAt": "2021-04-19T21:59:45+00:00",
251                            "updatedAt": "2021-04-19T21:59:45+00:00",
252                            "version": 1
253                        }
254                    }
255                ]
256            }
257        });
258
259        Mock::given(method("GET"))
260            .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+"))
261            .and(header("Authorization", "Bearer sessiontoken"))
262            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
263            .expect(1)
264            .mount(&mock_server)
265            .await;
266
267        let res = mangadex_client
268            .manga()
269            .draft()
270            .id(manga_id)
271            .get()
272            .include(ReferenceExpansionResource::Author)
273            .send()
274            .await?;
275
276        assert_eq!(res.data.relationships[0].type_, RelationshipType::Author);
277        assert!(res.data.relationships[0].related.is_none());
278        match res.data.relationships[0].attributes.as_ref().unwrap() {
279            RelatedAttributes::Author(author) => assert_eq!(author.name, "Hologfx".to_string()),
280            _ => panic!("Expected author RelatedAttributes"),
281        }
282
283        Ok(())
284    }
285
286    #[tokio::test]
287    async fn get_manga_draft_requires_auth() -> anyhow::Result<()> {
288        let mock_server = MockServer::start().await;
289        let http_client: HttpClient = HttpClient::builder()
290            .base_url(Url::parse(&mock_server.uri())?)
291            .build()?;
292        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
293
294        let manga_id = Uuid::new_v4();
295        let error_id = Uuid::new_v4();
296        let response_body = json!({
297            "result": "error",
298            "errors": [{
299                "id": error_id.to_string(),
300                "status": 403,
301                "title": "Forbidden",
302                "detail": "You must be logged in to continue."
303            }]
304        });
305
306        Mock::given(method("GET"))
307            .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+"))
308            .respond_with(ResponseTemplate::new(403).set_body_json(response_body))
309            .expect(0)
310            .mount(&mock_server)
311            .await;
312
313        let res = mangadex_client
314            .manga()
315            .draft()
316            .id(manga_id)
317            .get()
318            .send()
319            .await
320            .expect_err("expected error");
321
322        match res {
323            Error::MissingTokens => {}
324            _ => panic!("unexpected error: {:#?}", res),
325        }
326
327        Ok(())
328    }
329}