Compare commits
No commits in common. "feat-switch-to-surf-instead-of-reqwest" and "main" have entirely different histories.
feat-switc
...
main
|
@ -5,11 +5,10 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
request = ["surf/h1-client-rustls"]
|
||||
request = []
|
||||
convert_from_notion = []
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.12.0"
|
||||
async-trait = "0.1.68"
|
||||
base64 = "0.21.0"
|
||||
chrono = "0.4.31"
|
||||
|
@ -17,6 +16,9 @@ futures-core = "0.3.28"
|
|||
lazy_static = "1.4.0"
|
||||
log = "0.4.20"
|
||||
regex = "1.7.1"
|
||||
reqwest = { version = "0.11.14", features = ["json"] }
|
||||
serde = { version = "^1.0", features = ["derive"], default-features = false }
|
||||
serde_json = { version = "^1.0", features = ["raw_value"], default-features = false }
|
||||
surf = { version = "2.3.2", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.28.1", features = ["macros"] }
|
||||
|
|
208
src/lib.rs
208
src/lib.rs
|
@ -4,11 +4,12 @@ use std::sync::Arc;
|
|||
use chrono::{DateTime, NaiveTime, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
#[cfg(feature = "request")]
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use serde::de::Error as SerdeError;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
use surf::http::StatusCode;
|
||||
|
||||
use futures_core::future::BoxFuture;
|
||||
|
||||
|
@ -21,16 +22,18 @@ lazy_static! {
|
|||
const NOTION_VERSION: &str = "2022-06-28";
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type Callback = dyn Fn(surf::RequestBuilder) -> BoxFuture<'static, std::result::Result<surf::Response, surf::Error>>
|
||||
pub type Callback = dyn Fn(
|
||||
&mut reqwest::RequestBuilder,
|
||||
) -> BoxFuture<'_, std::result::Result<reqwest::Response, reqwest::Error>>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Http(StatusCode, surf::Response),
|
||||
Surf(surf::Error),
|
||||
Http(reqwest::Error, Option<Value>),
|
||||
Deserialization(serde_json::Error, Option<Value>),
|
||||
Header(reqwest::header::InvalidHeaderValue),
|
||||
ChronoParse(chrono::ParseError),
|
||||
UnexpectedType,
|
||||
}
|
||||
|
@ -41,9 +44,15 @@ impl std::fmt::Display for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<surf::Error> for Error {
|
||||
fn from(error: surf::Error) -> Self {
|
||||
Error::Surf(error)
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
Error::Http(error, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::header::InvalidHeaderValue> for Error {
|
||||
fn from(error: reqwest::header::InvalidHeaderValue) -> Self {
|
||||
Error::Header(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,31 +68,37 @@ impl From<chrono::ParseError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
// async fn try_to_parse_response<T: std::fmt::Debug + for<'de> serde::Deserialize<'de>>(
|
||||
// mut response: surf::Response,
|
||||
// ) -> Result<T> {
|
||||
// let text = response.body_string().await?;
|
||||
async fn try_to_parse_response<T: std::fmt::Debug + for<'de> serde::Deserialize<'de>>(
|
||||
response: reqwest::Response,
|
||||
) -> Result<T> {
|
||||
let text = response.text().await?;
|
||||
|
||||
// match serde_json::from_str::<T>(&text) {
|
||||
// Ok(value) => Ok(value),
|
||||
// Err(error) => match serde_json::from_str::<Value>(&text) {
|
||||
// Ok(body) => Err(Error::Deserialization(error, Some(body))),
|
||||
// _ => Err(Error::Deserialization(error, Some(Value::String(text)))),
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
match serde_json::from_str::<T>(&text) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(error) => match serde_json::from_str::<Value>(&text) {
|
||||
Ok(body) => Err(Error::Deserialization(error, Some(body))),
|
||||
_ => Err(Error::Deserialization(error, Some(Value::String(text)))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "request")]
|
||||
fn get_http_client(notion_api_key: &str) -> surf::Client {
|
||||
log::trace!("Readying HTTP Client");
|
||||
surf::Config::new()
|
||||
.add_header("Authorization", format!("Bearer {notion_api_key}"))
|
||||
.expect("to add Authorization header")
|
||||
.add_header("Notion-Version", NOTION_VERSION)
|
||||
.expect("to add Notion-Version header")
|
||||
.add_header("Content-Type", "application/json")
|
||||
.expect("to add Content-Type header")
|
||||
.try_into()
|
||||
fn get_http_client(notion_api_key: &str) -> reqwest::Client {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Bearer {notion_api_key}"))
|
||||
.expect("bearer token to be parsed into a header"),
|
||||
);
|
||||
headers.insert(
|
||||
"Notion-Version",
|
||||
HeaderValue::from_str(NOTION_VERSION).expect("notion version to be parsed into a header"),
|
||||
);
|
||||
headers.insert("Content-Type", HeaderValue::from_static("application/json"));
|
||||
|
||||
reqwest::ClientBuilder::new()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.expect("to build a valid client out of notion_api_key")
|
||||
}
|
||||
|
||||
|
@ -117,8 +132,8 @@ impl ClientBuilder {
|
|||
pub fn custom_request<F>(mut self, callback: F) -> Self
|
||||
where
|
||||
for<'c> F: Fn(
|
||||
surf::RequestBuilder,
|
||||
) -> BoxFuture<'static, std::result::Result<surf::Response, surf::Error>>
|
||||
&'c mut reqwest::RequestBuilder,
|
||||
) -> BoxFuture<'c, std::result::Result<reqwest::Response, reqwest::Error>>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
|
@ -132,11 +147,17 @@ impl ClientBuilder {
|
|||
pub fn build(self) -> Client {
|
||||
let notion_api_key = self.api_key.expect("api_key to be set");
|
||||
|
||||
let request_handler =
|
||||
self.custom_request
|
||||
.unwrap_or(Arc::new(|request_builder: surf::RequestBuilder| {
|
||||
Box::pin(request_builder)
|
||||
}));
|
||||
let request_handler = self.custom_request.unwrap_or(Arc::new(
|
||||
|request_builder: &mut reqwest::RequestBuilder| {
|
||||
Box::pin(async move {
|
||||
let request = request_builder
|
||||
.try_clone()
|
||||
.expect("non-stream body request clone to succeed");
|
||||
|
||||
request.send().await
|
||||
})
|
||||
},
|
||||
));
|
||||
|
||||
let http_client = Arc::from(get_http_client(¬ion_api_key));
|
||||
|
||||
|
@ -165,7 +186,7 @@ impl ClientBuilder {
|
|||
}
|
||||
|
||||
pub struct Client {
|
||||
http_client: Arc<surf::Client>,
|
||||
http_client: Arc<reqwest::Client>,
|
||||
request_handler: Arc<Callback>,
|
||||
|
||||
pub pages: Pages,
|
||||
|
@ -175,7 +196,6 @@ pub struct Client {
|
|||
}
|
||||
|
||||
impl<'a> Client {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> ClientBuilder {
|
||||
ClientBuilder::default()
|
||||
}
|
||||
|
@ -184,19 +204,19 @@ impl<'a> Client {
|
|||
self,
|
||||
options: SearchOptions<'b>,
|
||||
) -> Result<QueryResponse<T>> {
|
||||
let request = self
|
||||
let mut request = self
|
||||
.http_client
|
||||
.post("https://api.notion.com/v1/search")
|
||||
.body_json(&options)
|
||||
.expect("to parse JSON for doing `search`");
|
||||
.json(&options);
|
||||
|
||||
let mut response = (self.request_handler)(request)
|
||||
.await
|
||||
.expect("to request through a request handler");
|
||||
let response = (self.request_handler)(&mut request).await?;
|
||||
|
||||
match response.status() {
|
||||
StatusCode::Ok => Ok(response.body_json().await?),
|
||||
status => Err(Error::Http(status, response)),
|
||||
match response.error_for_status_ref() {
|
||||
Ok(_) => Ok(response.json().await?),
|
||||
Err(error) => {
|
||||
let body = response.json::<Value>().await?;
|
||||
Err(Error::Http(error, Some(body)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,30 +227,34 @@ pub struct PageOptions<'a> {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct Pages {
|
||||
http_client: Arc<surf::Client>,
|
||||
http_client: Arc<reqwest::Client>,
|
||||
request_handler: Arc<Callback>,
|
||||
}
|
||||
|
||||
impl Pages {
|
||||
pub async fn retrieve(self, options: PageOptions<'_>) -> Result<Page> {
|
||||
pub async fn retrieve<'a>(self, options: PageOptions<'a>) -> Result<Page> {
|
||||
let url = format!(
|
||||
"https://api.notion.com/v1/pages/{page_id}",
|
||||
page_id = options.page_id
|
||||
);
|
||||
|
||||
let request = self.http_client.get(url);
|
||||
let mut response = (self.request_handler)(request).await?;
|
||||
let mut request = self.http_client.get(url);
|
||||
|
||||
match response.status() {
|
||||
StatusCode::Ok => Ok(response.body_json().await?),
|
||||
status => Err(Error::Http(status, response)),
|
||||
let response = (self.request_handler)(&mut request).await?;
|
||||
|
||||
match response.error_for_status_ref() {
|
||||
Ok(_) => Ok(response.json().await?),
|
||||
Err(error) => {
|
||||
let body = response.json::<Value>().await?;
|
||||
Err(Error::Http(error, Some(body)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Blocks {
|
||||
http_client: Arc<surf::Client>,
|
||||
http_client: Arc<reqwest::Client>,
|
||||
request_handler: Arc<Callback>,
|
||||
}
|
||||
|
||||
|
@ -244,7 +268,7 @@ impl Blocks {
|
|||
}
|
||||
|
||||
pub struct BlockChildren {
|
||||
http_client: Arc<surf::Client>,
|
||||
http_client: Arc<reqwest::Client>,
|
||||
request_handler: Arc<Callback>,
|
||||
}
|
||||
|
||||
|
@ -253,26 +277,32 @@ pub struct BlockChildrenListOptions<'a> {
|
|||
}
|
||||
|
||||
impl BlockChildren {
|
||||
pub async fn list(self, options: BlockChildrenListOptions<'_>) -> Result<QueryResponse<Block>> {
|
||||
pub async fn list<'a>(
|
||||
self,
|
||||
options: BlockChildrenListOptions<'a>,
|
||||
) -> Result<QueryResponse<Block>> {
|
||||
let url = format!(
|
||||
"https://api.notion.com/v1/blocks/{block_id}/children",
|
||||
block_id = options.block_id
|
||||
);
|
||||
|
||||
let request = self.http_client.get(&url);
|
||||
let mut request = self.http_client.get(&url);
|
||||
|
||||
let mut response = (self.request_handler)(request).await?;
|
||||
let response = (self.request_handler)(&mut request).await?;
|
||||
|
||||
match response.status() {
|
||||
StatusCode::Ok => Ok(response.body_json().await?),
|
||||
status => Err(Error::Http(status, response)),
|
||||
match response.error_for_status_ref() {
|
||||
Ok(_) => Ok(response.json().await?),
|
||||
Err(error) => {
|
||||
let body = response.json::<Value>().await?;
|
||||
Err(Error::Http(error, Some(body)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Databases {
|
||||
http_client: Arc<surf::Client>,
|
||||
http_client: Arc<reqwest::Client>,
|
||||
request_handler: Arc<Callback>,
|
||||
}
|
||||
|
||||
|
@ -288,7 +318,11 @@ impl Databases {
|
|||
|
||||
let mut request = self.http_client.post(url);
|
||||
|
||||
let json = options.filter.map(|filter| json!({ "filter": filter }));
|
||||
let json = if let Some(filter) = options.filter {
|
||||
Some(json!({ "filter": filter }))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let json = if let Some(sorts) = options.sorts {
|
||||
if let Some(mut json) = json {
|
||||
|
@ -318,19 +352,18 @@ impl Databases {
|
|||
json
|
||||
};
|
||||
|
||||
if let Some(ref json) = json {
|
||||
request = request
|
||||
.body_json(json)
|
||||
.expect("to parse JSON for start_cursor");
|
||||
if let Some(json) = json {
|
||||
request = request.json(&json);
|
||||
}
|
||||
|
||||
log::trace!("Querying database with request: {request:#?} and body: {json:#?}");
|
||||
let response = (self.request_handler)(&mut request).await?;
|
||||
|
||||
let mut response = (self.request_handler)(request).await?;
|
||||
|
||||
match response.status() {
|
||||
StatusCode::Ok => Ok(response.body_json().await?),
|
||||
status => Err(Error::Http(status, response)),
|
||||
match response.error_for_status_ref() {
|
||||
Ok(_) => try_to_parse_response(response).await,
|
||||
Err(error) => {
|
||||
let body = try_to_parse_response::<Value>(response).await?;
|
||||
Err(Error::Http(error, Some(body)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,7 +372,7 @@ impl Databases {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn check_database_query() {
|
||||
let databases = Client::new()
|
||||
.api_key("secret_FuhJkAoOVZlk8YUT9ZOeYqWBRRZN6OMISJwhb4dTnud")
|
||||
|
@ -361,7 +394,7 @@ mod tests {
|
|||
println!("{databases:#?}");
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_blocks() {
|
||||
let blocks = Client::new()
|
||||
.api_key("secret_FuhJkAoOVZlk8YUT9ZOeYqWBRRZN6OMISJwhb4dTnud")
|
||||
|
@ -388,7 +421,7 @@ pub struct DatabaseQueryOptions<'a> {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct Users {
|
||||
http_client: Arc<surf::Client>,
|
||||
http_client: Arc<reqwest::Client>,
|
||||
request_handler: Arc<Callback>,
|
||||
}
|
||||
|
||||
|
@ -396,13 +429,16 @@ impl Users {
|
|||
pub async fn get(&self) -> Result<QueryResponse<User>> {
|
||||
let url = "https://api.notion.com/v1/users".to_owned();
|
||||
|
||||
let request = self.http_client.get(&url);
|
||||
let mut request = self.http_client.get(&url);
|
||||
|
||||
let mut response = (self.request_handler)(request).await?;
|
||||
let response = (self.request_handler)(&mut request).await?;
|
||||
|
||||
match response.status() {
|
||||
StatusCode::Ok => Ok(response.body_json().await?),
|
||||
status => Err(Error::Http(status, response)),
|
||||
match response.error_for_status_ref() {
|
||||
Ok(_) => Ok(response.json().await?),
|
||||
Err(error) => {
|
||||
let body = response.json::<Value>().await?;
|
||||
Err(Error::Http(error, Some(body)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -921,7 +957,7 @@ where
|
|||
serde_json::from_value::<DatabaseProperty>(value.to_owned()).unwrap_or_else(|error| {
|
||||
log::warn!(
|
||||
"Could not parse value because of error, defaulting to DatabaseProperty::Unsupported:\n= ERROR:\n{error:#?}\n= JSON:\n{:#?}\n---",
|
||||
serde_json::to_string_pretty(&value).expect("to pretty print the database property error")
|
||||
serde_json::to_string_pretty(&value).unwrap()
|
||||
);
|
||||
DatabaseProperty::Unsupported(value.to_owned())
|
||||
}),
|
||||
|
@ -947,7 +983,7 @@ pub struct Relation {
|
|||
// TODO: Paginate all possible responses
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct QueryResponse<T> {
|
||||
pub has_more: Option<bool>,
|
||||
pub has_more: bool,
|
||||
pub next_cursor: Option<String>,
|
||||
pub results: Vec<T>,
|
||||
}
|
||||
|
@ -1144,7 +1180,7 @@ where
|
|||
// Notion forgets to set the formula type, so we're doing it's homework
|
||||
"formula" => {
|
||||
if let Value::Object(object) = value {
|
||||
if object.get("type").is_none() {
|
||||
if let None = object.get("type") {
|
||||
object.insert("type".to_owned(), json!("string"));
|
||||
}
|
||||
}
|
||||
|
@ -1187,7 +1223,7 @@ where
|
|||
serde_json::from_value::<Property>(value.to_owned()).unwrap_or_else(|error| {
|
||||
log::warn!(
|
||||
"Could not parse value because of error, defaulting to Property::Unsupported:\n= ERROR:\n{error:#?}\n= JSON:\n{}\n---",
|
||||
serde_json::to_string_pretty(&value).expect("to pretty print Property errors")
|
||||
serde_json::to_string_pretty(&value).unwrap()
|
||||
);
|
||||
Property::Unsupported(value.to_owned())
|
||||
}),
|
||||
|
|
Loading…
Reference in a new issue