connpass_rs/
client.rs

1//! Sends requets to connpass API server with queries.
2//! This module provides non-blocking API (on tokio runtime) normally, but when `blocking` feature is enabled, additionally start to provide blocking API.
3//! These clients are internally using `reqwest` crate.
4
5use once_cell::sync::Lazy;
6use reqwest::{header::USER_AGENT, Client, Response, StatusCode};
7
8use crate::{
9    errors::{ConnpassCliError, ConnpassResult, HttpResponseError},
10    query::Query,
11    response::ConnpassResponse,
12};
13
14const BASE_URL: &str = "https://connpass.com/api/v1/event/";
15static CRATE_USER_AGENT: Lazy<String> = Lazy::new(|| {
16    format!(
17        "connpass-rs/{} (+https://github.com/yuk1ty/connpass-rs)",
18        env!("CARGO_PKG_VERSION")
19    )
20});
21
22/// Async API client for accessing and fetching data from connpass API.
23#[derive(Clone)]
24pub struct ConnpassClient {
25    client: Client,
26}
27
28impl Default for ConnpassClient {
29    fn default() -> Self {
30        ConnpassClient {
31            client: Client::new(),
32        }
33    }
34}
35
36impl ConnpassClient {
37    pub fn new() -> Self {
38        ConnpassClient::default()
39    }
40
41    /// Initializes client with your own client.
42    pub fn with_client(client: Client) -> Self {
43        ConnpassClient { client }
44    }
45
46    /// Sends requests and gets response from API.
47    /// The response is internally converted to `response::ConnpassResponse` with handling errors.
48    ///
49    /// # Arguments
50    /// If no condition is set to `query` and it's passed, the default options are applied.
51    /// The defaults are described in the connpass API specification page.
52    ///
53    /// # Example:
54    /// ```
55    /// use connpass_rs::{client::ConnpassClient, query::builder::QueryBuilder};
56    ///
57    /// #[tokio::main]
58    /// async fn main() {
59    ///     // fetch https://rust.connpass.com/event/228732/
60    ///     let query = QueryBuilder::begin().event_id(228732).build();
61    ///     if let Ok(query) = query {
62    ///         let client = ConnpassClient::new();
63    ///         let res = client.send_request(query).await;
64    ///         match res {
65    ///             Ok(r) => println!("{:?}", r),
66    ///             Err(err) => eprintln!("{:?}", err),
67    ///         }
68    ///     }
69    /// }
70    /// ```
71    pub async fn send_request(self, query: Query) -> ConnpassResult<ConnpassResponse> {
72        let response = self
73            .client
74            .get(BASE_URL)
75            .header(USER_AGENT, CRATE_USER_AGENT.as_str())
76            .query(&query.make_reqwest_query())
77            .send()
78            .await
79            .map_err(|err| ConnpassCliError::HttpResponse(HttpResponseError::ReqwestError(err)))?;
80        self.handler(response).await
81    }
82
83    async fn handler(&self, res: Response) -> ConnpassResult<ConnpassResponse> {
84        dbg!("response = {}", &res);
85        match res.status() {
86            StatusCode::OK => res.json::<ConnpassResponse>().await.map_err(|err| {
87                ConnpassCliError::HttpResponse(HttpResponseError::JsonDecode(format!("{}", err)))
88            }),
89            StatusCode::FORBIDDEN => {
90                Err(ConnpassCliError::HttpResponse(HttpResponseError::Forbidden))
91            }
92            StatusCode::INTERNAL_SERVER_ERROR => Err(ConnpassCliError::HttpResponse(
93                HttpResponseError::InternalServerError,
94            )),
95            StatusCode::SERVICE_UNAVAILABLE => Err(ConnpassCliError::HttpResponse(
96                HttpResponseError::ServiceUnavailable,
97            )),
98            s => Err(ConnpassCliError::HttpResponse(HttpResponseError::Various(
99                format!("Unexpected response received: {:?} (status code)", s),
100            ))),
101        }
102    }
103}
104
105/// The client using blokcing. This one capitalizes on `reqwest::blocking` API.
106#[cfg(feature = "blocking")]
107pub mod blocking {
108    use reqwest::{
109        blocking::{Client, Response},
110        header::USER_AGENT,
111        StatusCode,
112    };
113
114    use crate::{
115        errors::{ConnpassCliError, ConnpassResult, HttpResponseError},
116        query::Query,
117        response::ConnpassResponse,
118    };
119
120    use super::{BASE_URL, CRATE_USER_AGENT};
121
122    /// Blocking API client for accessing and fetching data from connpass.com
123    pub struct ConnpassClient {
124        client: Client,
125    }
126
127    impl Default for ConnpassClient {
128        fn default() -> Self {
129            ConnpassClient {
130                client: Client::new(),
131            }
132        }
133    }
134
135    impl ConnpassClient {
136        pub fn new() -> Self {
137            ConnpassClient::default()
138        }
139
140        /// Initializes client with your own client.
141        pub fn with_client(client: Client) -> Self {
142            ConnpassClient { client }
143        }
144
145        /// Sends requests and gets response from API in the blocking context.
146        /// The response is internally converted to `response::ConnpassResponse` with handling errors.
147        ///
148        /// # Arguments
149        /// If no condition is set to `query` and it's passed, the default options are applied.
150        /// The defaults are described in the connpass API specification page.
151        ///
152        /// # Example:
153        /// ```
154        /// use connpass_rs::{client::blocking::ConnpassClient, query::builder::QueryBuilder};
155        ///
156        /// fn main() {
157        ///     // fetch https://rust.connpass.com/event/228732/
158        ///     let query = QueryBuilder::begin().event_id(228732).build();
159        ///     if let Ok(query) = query {
160        ///         let client = ConnpassClient::new();
161        ///         let res = client.send_request(query);
162        ///         match res {
163        ///             Ok(r) => println!("{:?}", r),
164        ///             Err(err) => eprintln!("{:?}", err),
165        ///         }
166        ///     }
167        /// }
168        /// ```
169        #[allow(clippy::needless_doctest_main)]
170        pub fn send_request(self, query: Query) -> ConnpassResult<ConnpassResponse> {
171            let response = self
172                .client
173                .get(BASE_URL)
174                .header(USER_AGENT, CRATE_USER_AGENT.as_str())
175                .query(&query.make_reqwest_query())
176                .send()
177                .map_err(|err| {
178                    ConnpassCliError::HttpResponse(HttpResponseError::ReqwestError(err))
179                })?;
180
181            self.handler(response)
182        }
183
184        fn handler(&self, res: Response) -> ConnpassResult<ConnpassResponse> {
185            dbg!("response = {}", &res);
186            match res.status() {
187                StatusCode::OK => res.json::<ConnpassResponse>().map_err(|err| {
188                    ConnpassCliError::HttpResponse(HttpResponseError::JsonDecode(format!(
189                        "{}",
190                        err
191                    )))
192                }),
193                StatusCode::FORBIDDEN => {
194                    Err(ConnpassCliError::HttpResponse(HttpResponseError::Forbidden))
195                }
196                StatusCode::INTERNAL_SERVER_ERROR => Err(ConnpassCliError::HttpResponse(
197                    HttpResponseError::InternalServerError,
198                )),
199                StatusCode::SERVICE_UNAVAILABLE => Err(ConnpassCliError::HttpResponse(
200                    HttpResponseError::ServiceUnavailable,
201                )),
202                s => Err(ConnpassCliError::HttpResponse(HttpResponseError::Various(
203                    format!("Unexpected response received: {:?} (status code)", s),
204                ))),
205            }
206        }
207    }
208}