aboutsummaryrefslogtreecommitdiff
path: root/src/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/models')
-rw-r--r--src/models/error.rs70
-rw-r--r--src/models/properties.rs42
-rw-r--r--src/models/properties/formulas.rs27
-rw-r--r--src/models/properties/tests.rs28
-rw-r--r--src/models/properties/tests/date_property.json8
-rw-r--r--src/models/properties/tests/formula_date_value.json11
-rw-r--r--src/models/properties/tests/formula_number_value.json8
-rw-r--r--src/models/properties/tests/null_select_property.json5
-rw-r--r--src/models/properties/tests/select_property.json9
-rw-r--r--src/models/search.rs5
-rw-r--r--src/models/tests/error.json6
-rw-r--r--src/models/tests/search_results.json209
-rw-r--r--src/models/tests/unknown_error.json6
-rw-r--r--src/models/users.rs34
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,
+ },
+}