From 19d87e6ee3ae44c5b1c853adf00329a894778b06 Mon Sep 17 00:00:00 2001 From: Jake Swenson Date: Sun, 16 May 2021 22:23:37 -0700 Subject: [PATCH] feat: adding proper error type with snafu (#8) --- Cargo.toml | 1 + src/lib.rs | 56 ++++++++++++++++++++++++++++------------ src/models/properties.rs | 4 +-- src/models/search.rs | 26 +++++++++---------- 4 files changed, 56 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a763aa5..8d53ed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT" [dependencies] serde_json = "1.0.64" +snafu = "0.6.10" [dependencies.chrono] version = "0.4.19" diff --git a/src/lib.rs b/src/lib.rs index c97ee48..c74aadf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; +#[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 @@ -22,40 +38,48 @@ pub struct NotionApi { } impl NotionApi { - pub fn new(api_token: String) -> Result { + pub fn new(api_token: String) -> Result { 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(request: RequestBuilder) -> Result + async fn make_json_request(request: RequestBuilder) -> Result 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::(&json)?); + dbg!(serde_json::from_str::(&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, Box> { + pub async fn list_databases(&self) -> Result, Error> { let builder = self.client.get("https://api.notion.com/v1/databases"); Ok(NotionApi::make_json_request(builder).await?) @@ -64,7 +88,7 @@ impl NotionApi { pub async fn search>( &self, query: T, - ) -> Result, NotionApiClientError> { + ) -> Result, Error> { Ok(NotionApi::make_json_request( self.client .post("https://api.notion.com/v1/search") @@ -76,7 +100,7 @@ impl NotionApi { pub async fn get_database>( &self, database_id: T, - ) -> Result { + ) -> Result { Ok(NotionApi::make_json_request(self.client.get(format!( "https://api.notion.com/v1/databases/{}", database_id.id().id() @@ -88,7 +112,7 @@ impl NotionApi { &self, database: D, query: T, - ) -> Result, NotionApiClientError> + ) -> Result, Error> where T: Into, D: Identifiable, diff --git a/src/models/properties.rs b/src/models/properties.rs index c55ce1a..22689e9 100644 --- a/src/models/properties.rs +++ b/src/models/properties.rs @@ -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 }, @@ -292,7 +292,7 @@ pub enum PropertyValue { id: PropertyId, checkbox: bool, }, - URL { + Url { id: PropertyId, url: String, }, diff --git a/src/models/search.rs b/src/models/search.rs index 6acf847..6de3c8a 100644 --- a/src/models/search.rs +++ b/src/models/search.rs @@ -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 { @@ -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, + pub property: Option, /// The name of the timestamp to sort against. - timestamp: Option, - direction: SortDirection, + pub timestamp: Option, + pub direction: SortDirection, } #[derive(Serialize, Debug, Eq, PartialEq, Default)] @@ -322,13 +322,13 @@ impl From 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() }, }