mangadex_api/v5/chapter/id/
get.rs

1//! Builder for the chapter view endpoint.
2//!
3//! <https://api.mangadex.org/docs/swagger.html#/Chapter/get-chapter-id>
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 chapter_id = Uuid::new_v4();
16//! let chapter_res = client
17//!     .chapter()
18//!     .id(chapter_id)
19//!     .get()
20//!     .send()
21//!     .await?;
22//!
23//! println!("chapter: {:?}", chapter_res);
24//! # Ok(())
25//! # }
26//! ```
27
28use derive_builder::Builder;
29use serde::Serialize;
30use uuid::Uuid;
31
32use crate::HttpClientRef;
33use mangadex_api_schema::v5::ChapterResponse;
34use mangadex_api_types::ReferenceExpansionResource;
35
36#[cfg_attr(
37    feature = "deserializable-endpoint",
38    derive(serde::Deserialize, getset::Getters, getset::Setters)
39)]
40#[derive(Debug, Serialize, Clone, Builder)]
41#[serde(rename_all = "camelCase")]
42#[builder(
43    setter(into, strip_option),
44    build_fn(error = "mangadex_api_types::error::BuilderError")
45)]
46pub struct GetChapter {
47    /// This should never be set manually as this is only for internal use.
48    #[doc(hidden)]
49    #[serde(skip)]
50    #[builder(pattern = "immutable")]
51    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
52    pub http_client: HttpClientRef,
53
54    #[serde(skip_serializing)]
55    pub chapter_id: Uuid,
56
57    #[builder(setter(each = "include"), default)]
58    pub includes: Vec<ReferenceExpansionResource>,
59}
60
61endpoint! {
62    GET ("/chapter/{}", chapter_id),
63    #[query] GetChapter,
64    #[flatten_result] ChapterResponse,
65    GetChapterBuilder
66}
67
68#[cfg(test)]
69mod tests {
70    use fake::faker::name::en::Name;
71    use fake::Fake;
72    use serde_json::json;
73    use time::OffsetDateTime;
74    use url::Url;
75    use uuid::Uuid;
76    use wiremock::matchers::{method, path_regex};
77    use wiremock::{Mock, MockServer, ResponseTemplate};
78
79    use crate::{HttpClient, MangaDexClient};
80    use mangadex_api_types::error::Error;
81    use mangadex_api_types::{Language, MangaDexDateTime, ResponseType};
82
83    #[tokio::test]
84    async fn get_chapter_fires_a_request_to_base_url() -> anyhow::Result<()> {
85        let mock_server = MockServer::start().await;
86        let http_client = HttpClient::builder()
87            .base_url(Url::parse(&mock_server.uri())?)
88            .build()?;
89        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
90
91        let chapter_id = Uuid::new_v4();
92        let uploader_id = Uuid::new_v4();
93        let chapter_title: String = Name().fake();
94
95        let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
96
97        let response_body = json!({
98            "result": "ok",
99            "response": "entity",
100            "data": {
101                "id": chapter_id,
102                "type": "chapter",
103                "attributes": {
104                    "title": chapter_title,
105                    "volume": "1",
106                    "chapter": "1.5",
107                    "pages": 4,
108                    "translatedLanguage": "en",
109                    "uploader": uploader_id,
110                    "version": 1,
111                    "createdAt": datetime.to_string(),
112                    "updatedAt": datetime.to_string(),
113                    "publishAt": datetime.to_string(),
114                    "readableAt": datetime.to_string(),
115                },
116                "relationships": []
117            }
118        });
119
120        Mock::given(method("GET"))
121            .and(path_regex(r"/chapter/[0-9a-fA-F-]+"))
122            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
123            .expect(1)
124            .mount(&mock_server)
125            .await;
126
127        let res = mangadex_client
128            .chapter()
129            .id(chapter_id)
130            .get()
131            .build()?
132            .send()
133            .await?;
134
135        assert_eq!(res.response, ResponseType::Entity);
136        assert_eq!(res.data.id, chapter_id);
137        assert_eq!(res.data.attributes.title, Some(chapter_title));
138        assert_eq!(res.data.attributes.volume, Some("1".to_string()));
139        assert_eq!(res.data.attributes.chapter, Some("1.5".to_string()));
140        assert_eq!(res.data.attributes.pages, 4);
141        assert_eq!(res.data.attributes.translated_language, Language::English);
142        assert_eq!(res.data.attributes.version, 1);
143        assert_eq!(
144            res.data.attributes.created_at.to_string(),
145            datetime.to_string()
146        );
147        assert_eq!(
148            res.data.attributes.updated_at.as_ref().unwrap().to_string(),
149            datetime.to_string()
150        );
151        assert_eq!(
152            res.data.attributes.publish_at.unwrap().to_string(),
153            datetime.to_string()
154        );
155
156        Ok(())
157    }
158
159    #[tokio::test]
160    async fn get_chapter_handles_404() -> anyhow::Result<()> {
161        let mock_server = MockServer::start().await;
162        let http_client: HttpClient = HttpClient::builder()
163            .base_url(Url::parse(&mock_server.uri())?)
164            .build()?;
165        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
166
167        let chapter_id = Uuid::new_v4();
168        let error_id = Uuid::new_v4();
169
170        let response_body = json!({
171            "result": "error",
172            "errors": [{
173                "id": error_id.to_string(),
174                "status": 404,
175                "title": "Not found",
176                "detail": "Chapter could not be found"
177            }]
178        });
179
180        Mock::given(method("GET"))
181            .and(path_regex(r"/chapter/[0-9a-fA-F-]+"))
182            .respond_with(ResponseTemplate::new(404).set_body_json(response_body))
183            .expect(1)
184            .mount(&mock_server)
185            .await;
186
187        let res = mangadex_client
188            .chapter()
189            .id(chapter_id)
190            .get()
191            .build()?
192            .send()
193            .await
194            .expect_err("expected error");
195
196        if let Error::Api(errors) = res {
197            assert_eq!(errors.errors.len(), 1);
198
199            assert_eq!(errors.errors[0].id, error_id);
200            assert_eq!(errors.errors[0].status, 404);
201            assert_eq!(errors.errors[0].title, Some("Not found".to_string()));
202            assert_eq!(
203                errors.errors[0].detail,
204                Some("Chapter could not be found".to_string())
205            );
206        }
207
208        Ok(())
209    }
210}