mangadex_api/v5/manga/manga_id/relation/id/
delete.rs

1//! Builder for deleting a Manga relation.
2//!
3//! This endpoint requires authentication.
4//!
5//! This removes the relationship between Manga.
6//!
7//! <https://api.mangadex.org/docs/swagger.html#/Manga/delete-manga-relation-id>
8//!
9//! # Examples
10//!
11//! ```rust
12//! use uuid::Uuid;
13//!
14//! use mangadex_api_types::MangaRelation;
15//! use mangadex_api::v5::MangaDexClient;
16//! // use mangadex_api_types::{Password, Username};
17//!
18//! # async fn run() -> anyhow::Result<()> {
19//! let client = MangaDexClient::default();
20//! /*
21//!
22//!     let _login_res = client
23//!         .auth()
24//!         .login()
25//!         .post()
26//!         .username(Username::parse("myusername")?)
27//!         .password(Password::parse("hunter23")?)
28//!         .send()
29//!         .await?;
30//!
31//!  */
32//!
33//!
34//! let manga_id = Uuid::new_v4();
35//! let target_manga_id = Uuid::new_v4();
36//! let res = client
37//!     .manga()
38//!     .manga_id(manga_id)
39//!     .relation()
40//!     .id(target_manga_id)
41//!     .delete()
42//!     .send()
43//!     .await?;
44//!
45//! println!("deleted manga relation: {:?}", res);
46//! # Ok(())
47//! # }
48//! ```
49
50use derive_builder::Builder;
51use serde::Serialize;
52use uuid::Uuid;
53
54use crate::HttpClientRef;
55use mangadex_api_schema::NoData;
56use mangadex_api_types::error::Result;
57
58#[cfg_attr(
59    feature = "deserializable-endpoint",
60    derive(serde::Deserialize, getset::Getters, getset::Setters)
61)]
62#[derive(Debug, Serialize, Clone, Builder, Default)]
63#[serde(rename_all = "camelCase")]
64#[builder(
65    setter(into),
66    build_fn(error = "mangadex_api_types::error::BuilderError")
67)]
68pub struct DeleteMangaRelation {
69    /// This should never be set manually as this is only for internal use.
70    #[doc(hidden)]
71    #[serde(skip)]
72    #[builder(pattern = "immutable")]
73    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
74    pub http_client: HttpClientRef,
75
76    #[serde(skip_serializing)]
77    pub manga_id: Uuid,
78    #[serde(skip_serializing)]
79    pub relation_id: Uuid,
80}
81
82endpoint! {
83    DELETE ("/manga/{}/relation/{}", manga_id, relation_id),
84    #[no_data auth] DeleteMangaRelation,
85    #[discard_result] Result<NoData>,
86    DeleteMangaRelationBuilder
87}
88
89#[cfg(test)]
90mod tests {
91    use serde_json::json;
92    use url::Url;
93    use uuid::Uuid;
94    use wiremock::matchers::{header, method, path_regex};
95    use wiremock::{Mock, MockServer, ResponseTemplate};
96
97    use crate::v5::AuthTokens;
98    use crate::{HttpClient, MangaDexClient};
99    use mangadex_api_types::error::Error;
100
101    #[tokio::test]
102    async fn delete_manga_relation_fires_a_request_to_base_url() -> anyhow::Result<()> {
103        let mock_server = MockServer::start().await;
104        let http_client = HttpClient::builder()
105            .base_url(Url::parse(&mock_server.uri())?)
106            .auth_tokens(AuthTokens {
107                session: "sessiontoken".to_string(),
108                refresh: "refreshtoken".to_string(),
109            })
110            .build()?;
111        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
112
113        let manga_id = Uuid::new_v4();
114        let relation_id = Uuid::new_v4();
115        let response_body = json!({
116            "result": "ok",
117        });
118
119        Mock::given(method("DELETE"))
120            .and(path_regex(r"/manga/[0-9a-fA-F-]+/relation/[0-9a-fA-F-]+"))
121            .and(header("Authorization", "Bearer sessiontoken"))
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            .manga()
129            .manga_id(manga_id)
130            .relation()
131            .id(relation_id)
132            .delete()
133            .send()
134            .await;
135
136        assert!(res.is_ok());
137
138        Ok(())
139    }
140
141    #[tokio::test]
142    async fn delete_manga_relation_requires_auth() -> anyhow::Result<()> {
143        let mock_server = MockServer::start().await;
144        let http_client: HttpClient = HttpClient::builder()
145            .base_url(Url::parse(&mock_server.uri())?)
146            .build()?;
147        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
148
149        let manga_id = Uuid::new_v4();
150        let relation_id = Uuid::new_v4();
151        let error_id = Uuid::new_v4();
152        let response_body = json!({
153            "result": "error",
154            "errors": [{
155                "id": error_id.to_string(),
156                "status": 403,
157                "title": "Forbidden",
158                "detail": "You must be logged in to continue."
159            }]
160        });
161
162        Mock::given(method("DELETE"))
163            .and(path_regex(r"/manga/[0-9a-fA-F-]+/relation/[0-9a-fA-F-]+"))
164            .respond_with(ResponseTemplate::new(403).set_body_json(response_body))
165            .expect(0)
166            .mount(&mock_server)
167            .await;
168
169        let res = mangadex_client
170            .manga()
171            .manga_id(manga_id)
172            .relation()
173            .id(relation_id)
174            .delete()
175            .send()
176            .await
177            .expect_err("expected error");
178
179        match res {
180            Error::MissingTokens => {}
181            _ => panic!("unexpected error: {:#?}", res),
182        }
183
184        Ok(())
185    }
186}