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_types::ApiClientProfile;
47
48type ApiClientResponse = crate::Result<mangadex_api_schema::v5::ApiClientData>;
49
50/// Create a new api client.
51///
52/// This requires authentication.
53///
54/// Makes a request to `POST /client`
55#[cfg_attr(
56    feature = "deserializable-endpoint",
57    derive(serde::Deserialize, getset::Getters, getset::Setters)
58)]
59#[derive(Debug, Serialize, Clone, Builder, Default)]
60#[serde(rename_all = "camelCase")]
61#[builder(
62    setter(into, strip_option),
63    build_fn(error = "crate::error::BuilderError")
64)]
65#[non_exhaustive]
66pub struct CreateClient {
67    /// This should never be set manually as this is only for internal use.
68    #[doc(hidden)]
69    #[serde(skip)]
70    #[builder(pattern = "immutable")]
71    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
72    pub http_client: HttpClientRef,
73
74    pub name: String,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    #[builder(default)]
77    pub description: Option<String>,
78    #[builder(default)]
79    pub profile: ApiClientProfile,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    #[builder(default = "Some(1)")]
82    pub version: Option<u32>,
83}
84
85endpoint! {
86    POST "/client",
87    #[body auth] CreateClient,
88    #[flatten_result] ApiClientResponse,
89    CreateClientBuilder
90}
91
92#[cfg(test)]
93mod tests {
94    use serde_json::json;
95    use time::OffsetDateTime;
96    use url::Url;
97    use uuid::Uuid;
98    use wiremock::matchers::{body_json, header, method, path};
99    use wiremock::{Mock, MockServer, ResponseTemplate};
100
101    use crate::v5::AuthTokens;
102    use crate::{HttpClient, MangaDexClient};
103    use mangadex_api_types::{ApiClientProfile, ApiClientState, MangaDexDateTime};
104    use serde::Serialize;
105
106    #[derive(Serialize, Clone)]
107    struct CreateClientTestBody {
108        pub name: String,
109        #[serde(skip_serializing_if = "Option::is_none")]
110        pub description: Option<String>,
111        #[serde(default)]
112        pub profile: ApiClientProfile,
113        #[serde(skip_serializing_if = "Option::is_none")]
114        pub version: Option<u32>,
115    }
116
117    #[tokio::test]
118    async fn create_client_fires_a_request_to_base_url() -> anyhow::Result<()> {
119        let mock_server = MockServer::start().await;
120        let http_client: HttpClient = HttpClient::builder()
121            .base_url(Url::parse(&mock_server.uri())?)
122            .auth_tokens(non_exhaustive::non_exhaustive!(AuthTokens {
123                session: "sessiontoken".to_string(),
124                refresh: "refreshtoken".to_string(),
125            }))
126            .build()?;
127        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
128
129        let client_id = Uuid::new_v4();
130        let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
131
132        let _expected_body = CreateClientTestBody {
133            name: "mangadex-api".to_string(),
134            description: Some("a test api for the mangadex-api".to_string()),
135            profile: ApiClientProfile::Personal,
136            version: Some(1),
137        };
138        let state = ApiClientState::Requested;
139        let response_body = json!({
140            "result": "ok",
141            "response": "entity",
142            "data": {
143                "id": client_id,
144                "type": "api_client",
145                "attributes": {
146                    "name": _expected_body.name,
147                    "description": _expected_body.description,
148                    "profile": _expected_body.profile,
149                    "externalClientId": null,
150                    "isActive": false,
151                    "state": state,
152                    "createdAt": datetime.to_string(),
153                    "updatedAt": datetime.to_string(),
154                    "version": 1
155                },
156                "relationships": []
157            }
158        });
159        Mock::given(method("POST"))
160            .and(path("/client"))
161            .and(header("Authorization", "Bearer sessiontoken"))
162            .and(header("Content-Type", "application/json"))
163            .and(body_json(_expected_body.clone()))
164            .respond_with(ResponseTemplate::new(201).set_body_json(response_body))
165            .expect(1)
166            .mount(&mock_server)
167            .await;
168
169        let mut req_binding = mangadex_client.client().post();
170        let req = req_binding.name(_expected_body.name.clone());
171        if let Some(description) = _expected_body.description.clone() {
172            req.description(description);
173        }
174        req.profile(_expected_body.profile);
175        if let Some(version) = _expected_body.version {
176            req.version(version);
177        }
178
179        let res = req.send().await?;
180        let data = res.data;
181        assert_eq!(data.id, client_id);
182        assert_eq!(data.attributes.name, _expected_body.name);
183        assert_eq!(data.attributes.description, _expected_body.description);
184        assert!(data.attributes.external_client_id.is_none());
185        assert!(!data.attributes.is_active);
186        assert_eq!(data.attributes.state, state);
187        assert_eq!(data.attributes.created_at.to_string(), datetime.to_string());
188        assert_eq!(data.attributes.updated_at.to_string(), datetime.to_string());
189        assert_eq!(data.attributes.profile, _expected_body.profile);
190        Ok(())
191    }
192}