Skip to main content

mangadex_api/v5/custom_list/id/
get.rs

1//! Builder for the custom list view endpoint.
2//!
3//! <https://api.mangadex.org/docs/swagger.html#/CustomList/get-list-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 list_id = Uuid::new_v4();
16//! let res = client
17//!     .custom_list()
18//!     .id(list_id)
19//!     .get()
20//!     .send()
21//!     .await?;
22//!
23//! println!("custom list: {:?}", res);
24//! # Ok(())
25//! # }
26//! ```
27
28use derive_builder::Builder;
29use mangadex_api_schema::v5::CustomListData;
30use mangadex_api_types::ReferenceExpansionResource;
31use serde::Serialize;
32use uuid::Uuid;
33
34use crate::HttpClientRef;
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 = "crate::error::BuilderError")
45)]
46#[non_exhaustive]
47pub struct GetCustomList {
48    /// This should never be set manually as this is only for internal use.
49    #[doc(hidden)]
50    #[serde(skip)]
51    #[builder(pattern = "immutable")]
52    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
53    pub http_client: HttpClientRef,
54
55    #[serde(skip_serializing)]
56    pub list_id: Uuid,
57
58    #[serde(skip_serializing)]
59    #[builder(default)]
60    pub with_auth: bool,
61
62    #[serde(skip_serializing_if = "Vec::is_empty")]
63    #[builder(setter(each = "include"), default)]
64    pub includes: Vec<ReferenceExpansionResource>,
65}
66
67endpoint! {
68    GET ("/list/{}", list_id),
69    #[query auth => with_auth] GetCustomList,
70    #[flatten_result] crate::Result<CustomListData>,
71    GetCustomListBuilder
72}
73
74#[cfg(test)]
75mod tests {
76    use fake::Fake;
77    use fake::faker::name::en::Name;
78    use mangadex_api_schema::v5::AuthTokens;
79    use serde_json::json;
80    use url::Url;
81    use uuid::Uuid;
82    use wiremock::matchers::{header, method, path_regex};
83    use wiremock::{Mock, MockServer, ResponseTemplate};
84
85    use crate::error::Error;
86    use crate::{HttpClient, MangaDexClient};
87    use mangadex_api_types::CustomListVisibility;
88
89    #[tokio::test]
90    async fn get_custom_list_fires_a_request_to_base_url() -> anyhow::Result<()> {
91        let mock_server = MockServer::start().await;
92        let http_client = HttpClient::builder()
93            .base_url(Url::parse(&mock_server.uri())?)
94            .build()?;
95        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
96
97        let list_id = Uuid::new_v4();
98        let list_name: String = Name().fake();
99        let response_body = json!({
100            "result": "ok",
101            "response": "entity",
102            "data": {
103                "id": list_id,
104                "type": "custom_list",
105                "attributes": {
106                    "name": list_name,
107                    "visibility": "private",
108                    "version": 1
109                },
110                "relationships": []
111            }
112        });
113
114        Mock::given(method("GET"))
115            .and(path_regex(r"/list/[0-9a-fA-F-]+"))
116            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
117            .expect(1)
118            .mount(&mock_server)
119            .await;
120
121        let res = mangadex_client
122            .custom_list()
123            .id(list_id)
124            .get()
125            .send()
126            .await?;
127
128        assert_eq!(res.data.id, list_id);
129        assert_eq!(res.data.attributes.name, list_name);
130        assert_eq!(
131            res.data.attributes.visibility,
132            CustomListVisibility::Private
133        );
134        assert_eq!(res.data.attributes.version, 1);
135
136        Ok(())
137    }
138
139    #[tokio::test]
140    async fn get_custom_list_handles_404() -> anyhow::Result<()> {
141        let mock_server = MockServer::start().await;
142        let http_client: HttpClient = HttpClient::builder()
143            .base_url(Url::parse(&mock_server.uri())?)
144            .build()?;
145        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
146
147        let list_id = Uuid::new_v4();
148        let error_id = Uuid::new_v4();
149
150        let response_body = json!({
151            "result": "error",
152            "errors": [{
153                "id": error_id.to_string(),
154                "status": 404,
155                "title": "Not found",
156                "detail": "CustomList could not be found"
157            }]
158        });
159
160        Mock::given(method("GET"))
161            .and(path_regex(r"/list/[0-9a-fA-F-]+"))
162            .respond_with(ResponseTemplate::new(404).set_body_json(response_body))
163            .expect(1)
164            .mount(&mock_server)
165            .await;
166
167        let res = mangadex_client
168            .custom_list()
169            .id(list_id)
170            .get()
171            .send()
172            .await
173            .expect_err("expected error");
174
175        if let Error::Api(errors) = res {
176            assert_eq!(errors.errors.len(), 1);
177
178            assert_eq!(errors.errors[0].id, error_id);
179            assert_eq!(errors.errors[0].status, 404);
180            assert_eq!(errors.errors[0].title, Some("Not found".to_string()));
181            assert_eq!(
182                errors.errors[0].detail,
183                Some("CustomList could not be found".to_string())
184            );
185        }
186
187        Ok(())
188    }
189
190    #[tokio::test]
191    async fn get_custom_list_handles_auth() -> anyhow::Result<()> {
192        let mock_server = MockServer::start().await;
193        let http_client: HttpClient = HttpClient::builder()
194            .base_url(Url::parse(&mock_server.uri())?)
195            .auth_tokens(non_exhaustive::non_exhaustive!(AuthTokens {
196                session: "sessiontoken".into(),
197                refresh: "refresh".into(),
198            }))
199            .build()?;
200        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
201
202        let list_id = Uuid::new_v4();
203        let error_id = Uuid::new_v4();
204
205        let response_body = json!({
206            "result": "error",
207            "errors": [{
208                "id": error_id.to_string(),
209                "status": 404,
210                "title": "Not found",
211                "detail": "CustomList could not be found"
212            }]
213        });
214
215        Mock::given(method("GET"))
216            .and(path_regex(r"/list/[0-9a-fA-F-]+"))
217            .and(header("Authorization", "Bearer sessiontoken"))
218            .respond_with(ResponseTemplate::new(404).set_body_json(response_body))
219            .expect(1)
220            .mount(&mock_server)
221            .await;
222
223        let res = mangadex_client
224            .custom_list()
225            .id(list_id)
226            .get()
227            .with_auth(true)
228            .send()
229            .await
230            .expect_err("expected error");
231
232        if let Error::Api(errors) = res {
233            assert_eq!(errors.errors.len(), 1);
234
235            assert_eq!(errors.errors[0].id, error_id);
236            assert_eq!(errors.errors[0].status, 404);
237            assert_eq!(errors.errors[0].title, Some("Not found".to_string()));
238            assert_eq!(
239                errors.errors[0].detail,
240                Some("CustomList could not be found".to_string())
241            );
242        }
243
244        Ok(())
245    }
246}