notion_wasi/backend/
reqwest_impl.rs1use 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#[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#[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}