mangadex_api/v5/report/
get.rs

1//! Builder for the report reasons list endpoint.
2//!
3//! <https://api.mangadex.org/docs/swagger.html#/Report/get-report-reasons-by-category>
4//!
5//! # Examples
6//!
7//! ```rust
8//! use mangadex_api_types::{ReportCategory, ReportStatus};
9//! use mangadex_api::v5::MangaDexClient;
10//!
11//! # async fn run() -> anyhow::Result<()> {
12//! let client = MangaDexClient::default();
13//!
14//! let res = client
15//!     .report()
16//!     .get()
17//!     .category(ReportCategory::Manga)
18//!     .send()
19//!     .await?;
20//!
21//! println!("reports: {:?}", res);
22//! # Ok(())
23//! # }
24//! ```
25
26use derive_builder::Builder;
27use serde::Serialize;
28
29use crate::HttpClientRef;
30use mangadex_api_schema::v5::UserReportsCollection;
31use mangadex_api_types::{
32    ReferenceExpansionResource, ReportCategory, ReportSortOrder, ReportStatus,
33};
34use uuid::Uuid;
35
36#[cfg_attr(
37    feature = "deserializable-endpoint",
38    derive(serde::Deserialize, getset::Getters, getset::Setters)
39)]
40#[derive(Debug, Serialize, Clone, Builder, Default)]
41#[serde(rename_all = "camelCase")]
42#[builder(
43    setter(into, strip_option),
44    default,
45    build_fn(error = "mangadex_api_types::error::BuilderError")
46)]
47pub struct ListReportsByUser {
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_if = "Option::is_none")]
55    pub limit: Option<u32>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub offset: Option<u32>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub category: Option<ReportCategory>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub object_id: Option<Uuid>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub reason_id: Option<Uuid>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub status: Option<ReportStatus>,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub order: Option<ReportSortOrder>,
68    #[builder(setter(each = "include"))]
69    #[serde(skip_serializing_if = "Vec::is_empty")]
70    pub includes: Vec<ReferenceExpansionResource>,
71}
72
73endpoint! {
74    GET "/report",
75    #[query auth] ListReportsByUser,
76    #[rate_limited] UserReportsCollection,
77    ListReportsByUserBuilder
78}
79
80#[cfg(test)]
81mod tests {
82    use serde_json::json;
83    use time::OffsetDateTime;
84    use url::Url;
85    use uuid::Uuid;
86    use wiremock::matchers::{method, path};
87    use wiremock::{Mock, MockServer, ResponseTemplate};
88
89    use crate::v5::AuthTokens;
90    use crate::{HttpClient, MangaDexClient};
91    use mangadex_api_types::{MangaDexDateTime, ReportCategory, ReportStatus, ResponseType};
92
93    #[tokio::test]
94    async fn list_reports_by_user_fires_a_request_to_base_url() -> anyhow::Result<()> {
95        let mock_server = MockServer::start().await;
96        let http_client = HttpClient::builder()
97            .base_url(Url::parse(&mock_server.uri())?)
98            .auth_tokens(AuthTokens {
99                session: "sessiontoken".to_string(),
100                refresh: "refreshtoken".to_string(),
101            })
102            .build()?;
103        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
104
105        let report_id = Uuid::new_v4();
106        let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
107
108        let response_body = json!({
109            "result": "ok",
110            "response": "collection",
111            "data": [
112                {
113                    "id": report_id,
114                    "type": "report",
115                    "attributes": {
116                        "details": "The manga was a troll submission.",
117                        "objectId": "2",
118                        "status": "accepted",
119                        "createdAt": datetime.to_string()
120                    },
121                    "relationships": []
122                }
123            ],
124            "limit": 10,
125            "offset": 0,
126            "total": 1
127        });
128
129        Mock::given(method("GET"))
130            .and(path("/report"))
131            .respond_with(
132                ResponseTemplate::new(200)
133                    .insert_header("x-ratelimit-retry-after", "1698723860")
134                    .insert_header("x-ratelimit-limit", "40")
135                    .insert_header("x-ratelimit-remaining", "39")
136                    .set_body_json(response_body),
137            )
138            .expect(1)
139            .mount(&mock_server)
140            .await;
141
142        let res = mangadex_client
143            .report()
144            .get()
145            .category(ReportCategory::Manga)
146            .send()
147            .await?;
148
149        assert_eq!(res.response, ResponseType::Collection);
150        assert_eq!(res.limit, 10);
151        assert_eq!(res.offset, 0);
152        assert_eq!(res.total, 1);
153        let report = &res.data[0];
154        assert_eq!(report.id, report_id);
155
156        assert_eq!(
157            report.attributes.details,
158            "The manga was a troll submission.".to_string()
159        );
160        assert_eq!(report.attributes.object_id, "2".to_string());
161        assert_eq!(report.attributes.status, ReportStatus::Accepted);
162
163        Ok(())
164    }
165}