mangadex_api/v5/at_home/server/id/
get.rs

1//! Builder for the MangaDex@Home node URL endpoint.
2//!
3//! <https://api.mangadex.org/docs/swagger.html#/AtHome/get-at-home-server-chapterId>
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 node_url_res = client
17//!     .at_home()
18//!     .server()
19//!     .id(chapter_id)
20//!     .get()
21//!     .force_port_443(true)
22//!     .build()?
23//!     .send()
24//!     .await?;
25//!
26//! println!("Node URL: {:?}", node_url_res);
27//! # Ok(())
28//! # }
29//! ```
30
31use derive_builder::Builder;
32use serde::Serialize;
33use uuid::Uuid;
34
35use crate::HttpClientRef;
36use mangadex_api_schema::v5::AtHomeServer;
37
38#[cfg_attr(
39    feature = "deserializable-endpoint",
40    derive(serde::Deserialize, getset::Getters, getset::Setters)
41)]
42#[derive(Debug, Serialize, Clone, Builder)]
43#[serde(rename_all = "camelCase")]
44#[builder(
45    setter(into, strip_option),
46    build_fn(error = "mangadex_api_types::error::BuilderError")
47)]
48pub struct GetAtHomeServer {
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 chapter_id: Uuid,
58
59    /// Force selecting from MangaDex@Home servers that use the standard HTTPS port 443.
60    ///
61    /// While the conventional port for HTTPS traffic is 443 and servers are encouraged to use it,
62    /// it is not a hard requirement as it technically isn't anything special.
63    ///
64    /// However, some misbehaving school/office network will at time block traffic to non-standard
65    /// ports, and setting this flag to true will ensure selection of a server that uses these.
66    #[builder(default)]
67    pub force_port_443: bool,
68}
69
70endpoint! {
71    GET ("/at-home/server/{}", chapter_id),
72    #[query] GetAtHomeServer,
73    #[rate_limited] AtHomeServer,
74    GetAtHomeServerBuilder
75}
76
77#[cfg(test)]
78mod tests {
79    use fake::faker::internet::en::Password;
80    use fake::Fake;
81    use mangadex_api_types::error::Error;
82    use serde_json::json;
83    use url::Url;
84    use uuid::Uuid;
85    use wiremock::matchers::{method, path_regex};
86    use wiremock::{Mock, MockServer, ResponseTemplate};
87
88    use crate::{HttpClient, MangaDexClient};
89
90    #[tokio::test]
91    async fn activate_fires_a_request_to_base_url() -> anyhow::Result<()> {
92        let mock_server = MockServer::start().await;
93        let http_client = HttpClient::builder()
94            .base_url(Url::parse(&mock_server.uri())?)
95            .build()?;
96        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
97
98        let chapter_id = Uuid::new_v4();
99        let hash: String = Password(16..24).fake();
100
101        let response_body = json!({
102            "result": "ok",
103            "baseUrl": "https://example.org",
104            "chapter": {
105                "hash": hash,
106                "data": [
107                    "1.jpg"
108                ],
109                "dataSaver": [
110                    "1.jpg"
111                ],
112            }
113        });
114
115        Mock::given(method("GET"))
116            .and(path_regex(r"/at-home/server/[0-9a-fA-F-]+"))
117            .respond_with(
118                ResponseTemplate::new(200)
119                    .insert_header("x-ratelimit-retry-after", "1698723860")
120                    .insert_header("x-ratelimit-limit", "40")
121                    .insert_header("x-ratelimit-remaining", "39")
122                    .set_body_json(response_body),
123            )
124            .expect(1)
125            .mount(&mock_server)
126            .await;
127
128        let resp = mangadex_client
129            .at_home()
130            .server()
131            .id(chapter_id)
132            .get()
133            .force_port_443(true)
134            .build()?
135            .send()
136            .await?;
137
138        let rate_limit = resp.rate_limit;
139
140        assert_eq!(rate_limit.limit, 40);
141        assert_eq!(rate_limit.remaining, 39);
142        println!("{}", rate_limit.retry_after);
143
144        let res = resp.body;
145
146        assert_eq!(res.base_url, Url::parse("https://example.org")?);
147        assert_eq!(res.chapter.hash, hash);
148        assert_eq!(res.chapter.data, vec!["1.jpg"]);
149        assert_eq!(res.chapter.data_saver, vec!["1.jpg"]);
150
151        Ok(())
152    }
153
154    #[tokio::test]
155    async fn handle_missing_header() -> anyhow::Result<()> {
156        let mock_server = MockServer::start().await;
157        let http_client = HttpClient::builder()
158            .base_url(Url::parse(&mock_server.uri())?)
159            .build()?;
160        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
161
162        let chapter_id = Uuid::new_v4();
163        let hash: String = Password(16..24).fake();
164
165        let response_body = json!({
166            "result": "ok",
167            "baseUrl": "https://example.org",
168            "chapter": {
169                "hash": hash,
170                "data": [
171                    "1.jpg"
172                ],
173                "dataSaver": [
174                    "1.jpg"
175                ],
176            }
177        });
178
179        Mock::given(method("GET"))
180            .and(path_regex(r"/at-home/server/[0-9a-fA-F-]+"))
181            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
182            .expect(1)
183            .mount(&mock_server)
184            .await;
185
186        let resp = mangadex_client
187            .at_home()
188            .server()
189            .id(chapter_id)
190            .get()
191            .force_port_443(true)
192            .build()?
193            .send()
194            .await
195            .unwrap_err();
196        if let Error::RateLimitParseError(_) = resp {
197            Ok(())
198        } else {
199            panic!("Invalid Error Received")
200        }
201    }
202}