Skip to content

Commit

Permalink
feat: adding proper error type with snafu (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeswenson committed May 17, 2021
1 parent a2dc79a commit 19d87e6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 31 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -9,6 +9,7 @@ license = "MIT"

[dependencies]
serde_json = "1.0.64"
snafu = "0.6.10"

[dependencies.chrono]
version = "0.4.19"
Expand Down
56 changes: 40 additions & 16 deletions src/lib.rs
Expand Up @@ -3,13 +3,29 @@ use crate::models::{Database, DatabaseId, ListResponse, Object, Page};
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{header, Client, ClientBuilder, RequestBuilder};
use serde::de::DeserializeOwned;
use snafu::{ResultExt, Snafu};

mod models;

const NOTION_API_VERSION: &'static str = "2021-05-13";
const NOTION_API_VERSION: &str = "2021-05-13";

// todo: replace with proper snafu error
pub type NotionApiClientError = Box<dyn std::error::Error>;
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Invalid Notion API Token: {}", source))]
InvalidApiToken {
source: reqwest::header::InvalidHeaderValue,
},
#[snafu(display("Unable to build reqwest HTTP client: {}", source))]
ErrorBuildingClient { source: reqwest::Error },
#[snafu(display("Error sending HTTP request: {}", source))]
RequestFailed { source: reqwest::Error },

#[snafu(display("Error reading response: {}", source))]
ResponseError { source: reqwest::Error },

#[snafu(display("Error parsing json response: {}", source))]
JsonParseError { source: serde_json::Error },
}

pub trait Identifiable {
// There should only be one way to identify an object
Expand All @@ -22,40 +38,48 @@ pub struct NotionApi {
}

impl NotionApi {
pub fn new(api_token: String) -> Result<Self, NotionApiClientError> {
pub fn new(api_token: String) -> Result<Self, Error> {
let mut headers = HeaderMap::new();
headers.insert(
"Notion-Version",
HeaderValue::from_static(NOTION_API_VERSION),
);

let mut auth_value = HeaderValue::from_str(&format!("Bearer {}", api_token))?;
let mut auth_value =
HeaderValue::from_str(&format!("Bearer {}", api_token)).context(InvalidApiToken)?;
auth_value.set_sensitive(true);
headers.insert(header::AUTHORIZATION, auth_value);

let client = ClientBuilder::new().default_headers(headers).build()?;
let client = ClientBuilder::new()
.default_headers(headers)
.build()
.context(ErrorBuildingClient)?;

Ok(Self { client })
}

async fn make_json_request<T>(request: RequestBuilder) -> Result<T, NotionApiClientError>
async fn make_json_request<T>(request: RequestBuilder) -> Result<T, Error>
where
T: DeserializeOwned,
{
let json = request.send().await?.text().await?;
let json = request
.send()
.await
.context(RequestFailed)?
.text()
.await
.context(ResponseError)?;
#[cfg(test)]
{
println!("JSON: {}", json);
dbg!(serde_json::from_str::<serde_json::Value>(&json)?);
dbg!(serde_json::from_str::<serde_json::Value>(&json).context(JsonParseError)?);
}
let result = serde_json::from_str(&json)?;
let result = serde_json::from_str(&json).context(JsonParseError)?;
Ok(result)
}

/// This method is apparently deprecated/"not recommended"
pub async fn list_databases(
&self,
) -> Result<ListResponse<Database>, Box<dyn std::error::Error>> {
pub async fn list_databases(&self) -> Result<ListResponse<Database>, Error> {
let builder = self.client.get("https://api.notion.com/v1/databases");

Ok(NotionApi::make_json_request(builder).await?)
Expand All @@ -64,7 +88,7 @@ impl NotionApi {
pub async fn search<T: Into<SearchRequest>>(
&self,
query: T,
) -> Result<ListResponse<Object>, NotionApiClientError> {
) -> Result<ListResponse<Object>, Error> {
Ok(NotionApi::make_json_request(
self.client
.post("https://api.notion.com/v1/search")
Expand All @@ -76,7 +100,7 @@ impl NotionApi {
pub async fn get_database<T: Identifiable<Type = DatabaseId>>(
&self,
database_id: T,
) -> Result<Database, NotionApiClientError> {
) -> Result<Database, Error> {
Ok(NotionApi::make_json_request(self.client.get(format!(
"https://api.notion.com/v1/databases/{}",
database_id.id().id()
Expand All @@ -88,7 +112,7 @@ impl NotionApi {
&self,
database: D,
query: T,
) -> Result<ListResponse<Page>, NotionApiClientError>
) -> Result<ListResponse<Page>, Error>
where
T: Into<DatabaseQuery>,
D: Identifiable<Type = DatabaseId>,
Expand Down
4 changes: 2 additions & 2 deletions src/models/properties.rs
Expand Up @@ -157,7 +157,7 @@ pub enum PropertyConfiguration {
Checkbox { id: PropertyId },
/// Represents a URL Property
/// See https://developers.notion.com/reference/database#url-configuration
URL { id: PropertyId },
Url { id: PropertyId },
/// Represents a Email Property
/// See https://developers.notion.com/reference/database#email-configuration
Email { id: PropertyId },
Expand Down Expand Up @@ -292,7 +292,7 @@ pub enum PropertyValue {
id: PropertyId,
checkbox: bool,
},
URL {
Url {
id: PropertyId,
url: String,
},
Expand Down
26 changes: 13 additions & 13 deletions src/models/search.rs
Expand Up @@ -17,13 +17,6 @@ pub enum SortTimestamp {
LastEditedTime,
}

#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum DatabaseSortTimestamp {
CreatedTime,
LastEditedTime,
}

#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum FilterValue {
Expand Down Expand Up @@ -276,15 +269,22 @@ pub struct FilterCondition {
pub condition: PropertyCondition,
}

#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum DatabaseSortTimestamp {
CreatedTime,
LastEditedTime,
}

#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
pub struct DatabaseSort {
// Todo: Should property and timestamp be mutually exclusive? (i.e a flattened enum?)
// the documentation is not clear:
// https://developers.notion.com/reference/post-database-query#post-database-query-sort
property: Option<String>,
pub property: Option<String>,
/// The name of the timestamp to sort against.
timestamp: Option<SortTimestamp>,
direction: SortDirection,
pub timestamp: Option<DatabaseSortTimestamp>,
pub direction: SortDirection,
}

#[derive(Serialize, Debug, Eq, PartialEq, Default)]
Expand Down Expand Up @@ -322,13 +322,13 @@ impl From<NotionSearch> for SearchRequest {
timestamp,
} => SearchRequest {
sort: Some(Sort {
direction,
timestamp,
direction,
}),
..Default::default()
},
NotionSearch::Filter { value, property } => SearchRequest {
filter: Some(Filter { value, property }),
NotionSearch::Filter { property, value } => SearchRequest {
filter: Some(Filter { property, value }),
..Default::default()
},
}
Expand Down

0 comments on commit 19d87e6

Please sign in to comment.