notion_wasi/backend/
reqwest_impl.rs

1use crate::models::{error::ErrorResponse, Object};
2use crate::{TClient, NOTION_API_VERSION};
3
4use async_trait::async_trait;
5use reqwest::header::{HeaderMap, HeaderValue};
6use reqwest::{header, Client as RClient, ClientBuilder, RequestBuilder};
7use tracing::Instrument;
8
9/// An wrapper Error type for all errors produced by the [`NotionApi`](NotionApi) client.
10#[derive(Debug, thiserror::Error)]
11pub enum Error {
12    #[error("Invalid Notion API Token: {}", source)]
13    InvalidApiToken { source: header::InvalidHeaderValue },
14
15    #[error("Unable to build reqwest HTTP client: {}", source)]
16    ErrorBuildingClient { source: reqwest::Error },
17
18    #[error("Error sending HTTP request: {}", source)]
19    RequestFailed {
20        #[from]
21        source: reqwest::Error,
22    },
23
24    #[error("Error reading response: {}", source)]
25    ResponseIoError { source: reqwest::Error },
26
27    #[error("Error parsing json response: {}", source)]
28    JsonParseError { source: serde_json::Error },
29
30    #[error("Unexpected API Response")]
31    UnexpectedResponse { response: Object },
32
33    #[error("API Error {}({}): {}", .error.code, .error.status, .error.message)]
34    ApiError { error: ErrorResponse },
35}
36
37/// An API client for Notion.
38/// Create a client by using [new(api_token: String)](Self::new()).
39#[derive(Clone)]
40pub struct Client {
41    client: RClient,
42}
43
44impl Client {
45    pub fn new(api_token: String) -> Result<Self, Error> {
46        let mut headers = HeaderMap::new();
47        headers.insert(
48            "Notion-Version",
49            HeaderValue::from_static(NOTION_API_VERSION),
50        );
51
52        let mut auth_value = HeaderValue::from_str(&format!("Bearer {}", api_token))
53            .map_err(|source| Error::InvalidApiToken { source })?;
54        auth_value.set_sensitive(true);
55        headers.insert(header::AUTHORIZATION, auth_value);
56
57        let client = ClientBuilder::new()
58            .default_headers(headers)
59            .build()
60            .map_err(|source| Error::ErrorBuildingClient { source })?;
61
62        Ok(Self { client })
63    }
64}
65
66#[async_trait]
67impl TClient for Client {
68    async fn get<S: Into<String> + Send>(
69        &self,
70        uri: S,
71    ) -> crate::Result<Object> {
72        let url: String = uri.into();
73
74        let request = self.client.get(url);
75        self.make_json_request(request).await
76    }
77
78    async fn post<S: Into<String> + Send>(
79        &self,
80        uri: S,
81    ) -> crate::Result<Object> {
82        let url: String = uri.into();
83
84        let request = self.client.post(url);
85        self.make_json_request(request).await
86    }
87
88    async fn post_json<S: Into<String> + Send>(
89        &self,
90        uri: S,
91        body: &[u8],
92    ) -> crate::Result<Object> {
93        let url: String = uri.into();
94
95        let request = self
96            .client
97            .post(url)
98            .header("Content-Type", "application/json")
99            .header("Content-Length", body.len())
100            .body(body.to_owned());
101
102        self.make_json_request(request).await
103    }
104}
105
106impl Client {
107    async fn make_json_request(
108        &self,
109        request: RequestBuilder,
110    ) -> Result<Object, Error> {
111        let request = request.build()?;
112        let url = request.url();
113        tracing::trace!(
114            method = request.method().as_str(),
115            url = url.as_str(),
116            "Sending request"
117        );
118        let json = self
119            .client
120            .execute(request)
121            .instrument(tracing::trace_span!("Sending request"))
122            .await
123            .map_err(|source| Error::RequestFailed { source })?
124            .text()
125            .instrument(tracing::trace_span!("Reading response"))
126            .await
127            .map_err(|source| Error::ResponseIoError { source })?;
128
129        tracing::debug!("JSON Response: {}", json);
130        #[cfg(test)]
131        {
132            dbg!(serde_json::from_str::<serde_json::Value>(&json)
133                .map_err(|source| Error::JsonParseError { source })?);
134        }
135        let result =
136            serde_json::from_str(&json).map_err(|source| Error::JsonParseError { source })?;
137
138        match result {
139            Object::Error { error } => Err(Error::ApiError { error }),
140            response => Ok(response),
141        }
142    }
143}