mangadex_api/v5/api_client/
post.rs

1//! Builder for the create client endpoint.
2//!
3//! <https://api.mangadex.org/docs/swagger.html#/ApiClient/post-create-apiclient>
4//! <https://api.mangadex.org/docs/redoc.html#tag/ApiClient/operation/post-create-apiclient>
5//!
6//! ```rust
7//!
8//! use mangadex_api::MangaDexClient;
9//! // use mangadex_api_types::{Password, Username};
10//! use mangadex_api_types::ApiClientProfile;
11//!
12//! # async fn run() -> anyhow::Result<()> {
13//! let client = MangaDexClient::default();
14//!
15//! /*
16//! Put your login script here
17//!  
18//! let _login_res = client
19//!     .auth()
20//!     .login()
21//!     .username(Username::parse("myusername")?)
22//!     .password(Password::parse("hunter23")?)
23//!     .build()?
24//!     .send()
25//!     .await?;
26//! */
27//!
28//! let client_res = client
29//!     .client()
30//!     .post()
31//!     .name("My Client")
32//!     .profile(ApiClientProfile::Personal)
33//!     .description("It's my personal API Client for the mangadex-api :)")
34//!     .send()
35//!     .await?;
36//!
37//! println!("Client creation: {:?}", client_res);
38//! # Ok(())
39//! # }
40//! ```
41
42use derive_builder::Builder;
43use serde::Serialize;
44
45use crate::HttpClientRef;
46use mangadex_api_schema::v5::ApiClientResponse;
47use mangadex_api_types::ApiClientProfile;
48
49/// Create a new api client.
50///
51/// This requires authentication.
52///
53/// Makes a request to `POST /client`
54#[cfg_attr(
55    feature = "deserializable-endpoint",
56    derive(serde::Deserialize, getset::Getters, getset::Setters)
57)]
58#[derive(Debug, Serialize, Clone, Builder, Default)]
59#[serde(rename_all = "camelCase")]
60#[builder(
61    setter(into, strip_option),
62    build_fn(error = "mangadex_api_types::error::BuilderError")
63)]
64#[cfg_attr(feature = "non_exhaustive", non_exhaustive)]
65pub struct CreateClient {
66    /// This should never be set manually as this is only for internal use.
67    #[doc(hidden)]
68    #[serde(skip)]
69    #[builder(pattern = "immutable")]
70    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
71    pub http_client: HttpClientRef,
72
73    pub name: String,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    #[builder(default)]
76    pub description: Option<String>,
77    #[builder(default)]
78    pub profile: ApiClientProfile,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    #[builder(default = "Some(1)")]
81    pub version: Option<u32>,
82}
83
84endpoint! {
85    POST "/client",
86    #[body auth] CreateClient,
87    #[flatten_result] ApiClientResponse,
88    CreateClientBuilder
89}
90
91#[cfg(test)]
92mod tests {
93    use serde_json::json;
94    use time::OffsetDateTime;
95    use url::Url;
96    use uuid::Uuid;
97    use wiremock::matchers::{body_json, header, method, path};
98    use wiremock::{Mock, MockServer, ResponseTemplate};
99
100    use crate::v5::AuthTokens;
101    use crate::{HttpClient, MangaDexClient};
102    use mangadex_api_types::{ApiClientProfile, ApiClientState, MangaDexDateTime};
103    use serde::Serialize;
104
105    #[derive(Serialize, Clone)]
106    struct CreateClientTestBody {
107        pub name: String,
108        #[serde(skip_serializing_if = "Option::is_none")]
109        pub description: Option<String>,
110        #[serde(default)]
111        pub profile: ApiClientProfile,
112        #[serde(skip_serializing_if = "Option::is_none")]
113        pub version: Option<u32>,
114    }
115
116    #[tokio::test]
117    async fn create_client_fires_a_request_to_base_url() -> anyhow::Result<()> {
118        let mock_server = MockServer::start().await;
119        let http_client: HttpClient = HttpClient::builder()
120            .base_url(Url::parse(&mock_server.uri())?)
121            .auth_tokens(AuthTokens {
122                session: "sessiontoken".to_string(),
123                refresh: "refreshtoken".to_string(),
124            })
125            .build()?;
126        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
127
128        let client_id = Uuid::new_v4();
129        let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
130
131        let _expected_body = CreateClientTestBody {
132            name: "mangadex-api".to_string(),
133            description: Some("a test api for the mangadex-api".to_string()),
134            profile: ApiClientProfile::Personal,
135            version: Some(1),
136        };
137        let state = ApiClientState::Requested;
138        let response_body = json!({
139            "result": "ok",
140            "response": "entity",
141            "data": {
142                "id": client_id,
143                "type": "api_client",
144                "attributes": {
145                    "name": _expected_body.name,
146                    "description": _expected_body.description,
147                    "profile": _expected_body.profile,
148                    "externalClientId": null,
149                    "isActive": false,
150                    "state": state,
151                    "createdAt": datetime.to_string(),
152                    "updatedAt": datetime.to_string(),
153                    "version": 1
154                },
155                "relationships": []
156            }
157        });
158        Mock::given(method("POST"))
159            .and(path("/client"))
160            .and(header("Authorization", "Bearer sessiontoken"))
161            .and(header("Content-Type", "application/json"))
162            .and(body_json(_expected_body.clone()))
163            .respond_with(ResponseTemplate::new(201).set_body_json(response_body))
164            .expect(1)
165            .mount(&mock_server)
166            .await;
167
168        let mut req_binding = mangadex_client.client().post();
169        let req = req_binding.name(_expected_body.name.clone());
170        if let Some(description) = _expected_body.description.clone() {
171            req.description(description);
172        }
173        req.profile(_expected_body.profile);
174        if let Some(version) = _expected_body.version {
175            req.version(version);
176        }
177
178        let res = req.send().await?;
179        let data = res.data;
180        assert_eq!(data.id, client_id);
181        assert_eq!(data.attributes.name, _expected_body.name);
182        assert_eq!(data.attributes.description, _expected_body.description);
183        assert!(data.attributes.external_client_id.is_none());
184        assert!(!data.attributes.is_active);
185        assert_eq!(data.attributes.state, state);
186        assert_eq!(data.attributes.created_at.to_string(), datetime.to_string());
187        assert_eq!(data.attributes.updated_at.to_string(), datetime.to_string());
188        assert_eq!(data.attributes.profile, _expected_body.profile);
189        Ok(())
190    }
191}