diff options
Diffstat (limited to 'src/models')
-rw-r--r-- | src/models/error.rs | 70 | ||||
-rw-r--r-- | src/models/properties.rs | 42 | ||||
-rw-r--r-- | src/models/properties/formulas.rs | 27 | ||||
-rw-r--r-- | src/models/properties/tests.rs | 28 | ||||
-rw-r--r-- | src/models/properties/tests/date_property.json | 8 | ||||
-rw-r--r-- | src/models/properties/tests/formula_date_value.json | 11 | ||||
-rw-r--r-- | src/models/properties/tests/formula_number_value.json | 8 | ||||
-rw-r--r-- | src/models/properties/tests/null_select_property.json | 5 | ||||
-rw-r--r-- | src/models/properties/tests/select_property.json | 9 | ||||
-rw-r--r-- | src/models/search.rs | 5 | ||||
-rw-r--r-- | src/models/tests/error.json | 6 | ||||
-rw-r--r-- | src/models/tests/search_results.json | 209 | ||||
-rw-r--r-- | src/models/tests/unknown_error.json | 6 | ||||
-rw-r--r-- | src/models/users.rs | 34 |
14 files changed, 450 insertions, 18 deletions
diff --git a/src/models/error.rs b/src/models/error.rs new file mode 100644 index 0000000..e382719 --- /dev/null +++ b/src/models/error.rs @@ -0,0 +1,70 @@ +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +#[derive(Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] +#[serde(transparent)] +pub struct StatusCode(u16); + +impl StatusCode { + pub fn code(&self) -> u16 { + self.0 + } +} + +impl Display for StatusCode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +/// <https://developers.notion.com/reference/errors> +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct ErrorResponse { + pub status: StatusCode, + pub code: ErrorCode, + pub message: String, +} + +/// <https://developers.notion.com/reference/errors> +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ErrorCode { + InvalidJson, + InvalidRequestUrl, + InvalidRequest, + ValidationError, + MissionVersion, + Unauthorized, + RestrictedResource, + ObjectNotFound, + ConflictError, + RateLimited, + InternalServerError, + ServiceUnavailable, + #[serde(other)] // serde issue #912 + Unknown, +} + +impl Display for ErrorCode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(test)] +mod tests { + use crate::models::error::{ErrorCode, ErrorResponse}; + + #[test] + fn deserialize_error() { + let error: ErrorResponse = serde_json::from_str(include_str!("tests/error.json")).unwrap(); + assert_eq!(error.code, ErrorCode::ValidationError) + } + + #[test] + fn deserialize_unknown_error() { + let error: ErrorResponse = + serde_json::from_str(include_str!("tests/unknown_error.json")).unwrap(); + assert_eq!(error.code, ErrorCode::Unknown) + } +} diff --git a/src/models/properties.rs b/src/models/properties.rs index b3948b4..95ddef3 100644 --- a/src/models/properties.rs +++ b/src/models/properties.rs @@ -1,12 +1,15 @@ use crate::models::text::RichText; -use crate::models::{DatabaseId, PageId, User}; -use serde::{Deserialize, Serialize}; +use crate::models::users::User; use super::{DateTime, Number, Utc}; +use crate::ids::{DatabaseId, PageId, PropertyId}; +use chrono::NaiveDate; +use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone)] -#[serde(transparent)] -pub struct PropertyId(String); +pub mod formulas; + +#[cfg(test)] +mod tests; /// How the number is displayed in Notion. #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone, Hash)] @@ -179,7 +182,7 @@ pub enum PropertyConfiguration { /// See <https://developers.notion.com/reference/database#created-by-configuration> CreatedBy { id: PropertyId }, /// See <https://developers.notion.com/reference/database#last-edited-time-configuration> - LastEditTime { id: PropertyId }, + LastEditedTime { id: PropertyId }, /// See <https://developers.notion.com/reference/database#last-edited-by-configuration> LastEditBy { id: PropertyId }, } @@ -192,11 +195,16 @@ pub struct SelectedValue { } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(untagged)] +pub enum DateOrDateTime { + Date(NaiveDate), + DateTime(DateTime<Utc>), +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct DateValue { - // Todo: Will this work with dates (without time)? - // does there need to be an enum of Date|DateTime? - pub start: DateTime<Utc>, - pub end: Option<DateTime<Utc>>, + pub start: DateOrDateTime, + pub end: Option<DateOrDateTime>, } /// Formula property value objects represent the result of evaluating a formula @@ -205,10 +213,10 @@ pub struct DateValue { #[serde(tag = "type")] #[serde(rename_all = "snake_case")] pub enum FormulaResultValue { - String(#[serde(rename = "string")] Option<String>), - Number(#[serde(rename = "number")] Option<Number>), - Boolean(#[serde(rename = "boolean")] Option<bool>), - Date(#[serde(rename = "date")] Option<DateTime<Utc>>), + String { string: Option<String> }, + Number { number: Option<Number> }, + Boolean { boolean: Option<bool> }, + Date { date: Option<DateValue> }, } /// Relation property value objects contain an array of page references within the relation property. @@ -260,11 +268,11 @@ pub enum PropertyValue { /// <https://developers.notion.com/reference/page#select-property-values> Select { id: PropertyId, - select: SelectedValue, + select: Option<SelectedValue>, }, MultiSelect { id: PropertyId, - multi_select: Vec<SelectedValue>, + multi_select: Option<Vec<SelectedValue>>, }, Date { id: PropertyId, @@ -290,7 +298,7 @@ pub enum PropertyValue { }, Files { id: PropertyId, - files: Vec<FileReference>, + files: Option<Vec<FileReference>>, }, Checkbox { id: PropertyId, diff --git a/src/models/properties/formulas.rs b/src/models/properties/formulas.rs new file mode 100644 index 0000000..20bc7f4 --- /dev/null +++ b/src/models/properties/formulas.rs @@ -0,0 +1,27 @@ +#[cfg(test)] +mod tests { + use crate::models::properties::{FormulaResultValue, PropertyValue}; + + #[test] + fn parse_number_formula_prop() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/formula_number_value.json")).unwrap(); + } + + #[test] + fn parse_date_formula_prop() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/formula_date_value.json")).unwrap(); + } + + #[test] + fn parse_number_formula() { + let _value: FormulaResultValue = serde_json::from_str( + r#"{ + "type": "number", + "number": 0 + }"#, + ) + .unwrap(); + } +} diff --git a/src/models/properties/tests.rs b/src/models/properties/tests.rs new file mode 100644 index 0000000..23b376d --- /dev/null +++ b/src/models/properties/tests.rs @@ -0,0 +1,28 @@ +use super::{DateOrDateTime, PropertyValue}; +use chrono::NaiveDate; + +#[test] +fn verify_date_parsing() { + let date = NaiveDate::from_ymd(2021, 01, 02); + let result = serde_json::to_string(&DateOrDateTime::Date(date)).unwrap(); + let parsed: DateOrDateTime = serde_json::from_str(&result).unwrap(); + println!("{:?}", parsed); +} + +#[test] +fn parse_date_property() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/date_property.json")).unwrap(); +} + +#[test] +fn parse_null_select_property() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/null_select_property.json")).unwrap(); +} + +#[test] +fn parse_select_property() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/select_property.json")).unwrap(); +} diff --git a/src/models/properties/tests/date_property.json b/src/models/properties/tests/date_property.json new file mode 100644 index 0000000..fe2b04f --- /dev/null +++ b/src/models/properties/tests/date_property.json @@ -0,0 +1,8 @@ +{ + "id": "VXfM", + "type": "date", + "date": { + "start": "2021-09-30", + "end": null + } +} diff --git a/src/models/properties/tests/formula_date_value.json b/src/models/properties/tests/formula_date_value.json new file mode 100644 index 0000000..84728c5 --- /dev/null +++ b/src/models/properties/tests/formula_date_value.json @@ -0,0 +1,11 @@ +{ + "id": "7*%269", + "type": "formula", + "formula": { + "type": "date", + "date": { + "start": "2021-09-30", + "end": null + } + } +} diff --git a/src/models/properties/tests/formula_number_value.json b/src/models/properties/tests/formula_number_value.json new file mode 100644 index 0000000..2a831e3 --- /dev/null +++ b/src/models/properties/tests/formula_number_value.json @@ -0,0 +1,8 @@ +{ + "id": "abc", + "type": "formula", + "formula": { + "type": "number", + "number": 0 + } +} diff --git a/src/models/properties/tests/null_select_property.json b/src/models/properties/tests/null_select_property.json new file mode 100644 index 0000000..e0ed24e --- /dev/null +++ b/src/models/properties/tests/null_select_property.json @@ -0,0 +1,5 @@ +{ + "id": "uX", + "type": "select", + "select": null +} diff --git a/src/models/properties/tests/select_property.json b/src/models/properties/tests/select_property.json new file mode 100644 index 0000000..db65bdf --- /dev/null +++ b/src/models/properties/tests/select_property.json @@ -0,0 +1,9 @@ +{ + "id": "uX", + "type": "select", + "select": { + "id": "9c", + "name": "Reserved", + "color": "green" + } +} diff --git a/src/models/search.rs b/src/models/search.rs index aa2f62e..3e5d782 100644 --- a/src/models/search.rs +++ b/src/models/search.rs @@ -1,5 +1,6 @@ +use crate::ids::{PageId, UserId}; use crate::models::paging::Paging; -use crate::models::{Number, PageId, UserId}; +use crate::models::Number; use chrono::{DateTime, Utc}; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; @@ -281,8 +282,10 @@ 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 + #[serde(skip_serializing_if = "Option::is_none")] pub property: Option<String>, /// The name of the timestamp to sort against. + #[serde(skip_serializing_if = "Option::is_none")] pub timestamp: Option<DatabaseSortTimestamp>, pub direction: SortDirection, } diff --git a/src/models/tests/error.json b/src/models/tests/error.json new file mode 100644 index 0000000..0194824 --- /dev/null +++ b/src/models/tests/error.json @@ -0,0 +1,6 @@ +{ + "object": "error", + "status": 400, + "code": "validation_error", + "message": "Could not find property with name or id: LastEditedTime" +} diff --git a/src/models/tests/search_results.json b/src/models/tests/search_results.json new file mode 100644 index 0000000..ddeb27f --- /dev/null +++ b/src/models/tests/search_results.json @@ -0,0 +1,209 @@ +{ + "object": "list", + "results": [ + { + "object": "database", + "id": "58", + "cover": null, + "icon": { + "type": "emoji", + "emoji": "✈️" + }, + "created_time": "2019-05-18T20:28:00.000Z", + "last_edited_time": "2021-08-29T16:13:00.000Z", + "title": [ + { + "type": "text", + "text": { + "content": "Plans", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Plans", + "href": null + } + ], + "properties": { + "Places": { + "id": "6B", + "name": "Places", + "type": "rich_text", + "rich_text": {} + }, + "End Date": { + "id": "7*", + "name": "End Date", + "type": "formula", + "formula": { + "expression": "dateSubtract(end(prop(\"Date\")), hour(end(prop(\"Date\"))) * 60 + minute(end(prop(\"Date\"))), \"minutes\")" + } + }, + "Type": { + "id": "%3D", + "name": "Type", + "type": "select", + "select": { + "options": [ + { + "id": "f9", + "name": "Travel", + "color": "green" + }, + { + "id": "0b", + "name": "Event", + "color": "pink" + } + ] + } + }, + "People": { + "id": "G'", + "name": "People", + "type": "people", + "people": {} + }, + "Date": { + "id": "VX", + "name": "Date", + "type": "date", + "date": {} + }, + "Last Edited": { + "id": "ow", + "name": "Last Edited", + "type": "last_edited_time", + "last_edited_time": {} + }, + "Status": { + "id": "uX", + "name": "Status", + "type": "select", + "select": { + "options": [ + { + "id": "e4", + "name": "Time Block", + "color": "pink" + }, + { + "id": "27", + "name": "Idea", + "color": "purple" + }, + { + "id": "86", + "name": "Planning", + "color": "blue" + }, + { + "id": "fb", + "name": "Booked", + "color": "green" + }, + { + "id": "9c", + "name": "Reserved", + "color": "green" + }, + { + "id": "49", + "name": "Need Flights", + "color": "red" + }, + { + "id": "23", + "name": "Need Reservation", + "color": "red" + }, + { + "id": "2c", + "name": "Need Tickets", + "color": "red" + }, + { + "id": "69", + "name": "Need Hotel", + "color": "red" + }, + { + "id": "73", + "name": "Canceled", + "color": "red" + } + ] + } + }, + "Nights": { + "id": "%7", + "name": "Nights", + "type": "formula", + "formula": { + "expression": "dateBetween(end(prop(\"Date\")), start(prop(\"Date\")), \"days\")" + } + }, + "Name": { + "id": "title", + "name": "Name", + "type": "title", + "title": {} + } + }, + "parent": { + "type": "page_id", + "page_id": "12" + } + }, + { + "object": "page", + "id": "71", + "created_time": "2021-05-22T22:38:00.000Z", + "last_edited_time": "2021-05-31T17:09:00.000Z", + "cover": null, + "icon": { + "type": "emoji", + "emoji": "🎢" + }, + "parent": { + "type": "page_id", + "page_id": "7c" + }, + "archived": false, + "properties": { + "title": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Plans", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Plans", + "href": null + } + ] + } + }, + "url": "https://www.notion.so/Plans" + } + ], + "next_cursor": null, + "has_more": false +} diff --git a/src/models/tests/unknown_error.json b/src/models/tests/unknown_error.json new file mode 100644 index 0000000..0e30fea --- /dev/null +++ b/src/models/tests/unknown_error.json @@ -0,0 +1,6 @@ +{ + "object": "error", + "status": 400, + "code": "asadfsdfasd", + "message": "I made this up" +} diff --git a/src/models/users.rs b/src/models/users.rs new file mode 100644 index 0000000..b7702d0 --- /dev/null +++ b/src/models/users.rs @@ -0,0 +1,34 @@ +use crate::ids::UserId; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct UserCommon { + pub id: UserId, + pub name: Option<String>, + pub avatar_url: Option<String>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct Person { + pub email: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct Bot { + pub email: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum User { + Person { + #[serde(flatten)] + common: UserCommon, + person: Person, + }, + Bot { + #[serde(flatten)] + common: UserCommon, + bot: Bot, + }, +} |