notion_async_api/
object.rs

1use std::collections::BTreeMap;
2use std::fmt::Display;
3
4use chrono::{DateTime, Utc};
5use monostate::MustBe;
6use serde::{Deserialize, Serialize};
7use serde_with::serde_as;
8use thiserror::Error;
9
10use crate::misc::Unquotes;
11use crate::user::User;
12
13pub trait Object: Send {
14    fn id(&self) -> &str;
15    fn object_type(&self) -> ObjectType;
16}
17
18/// Common object info shared between [`Block`](crate::Block),
19/// [`Page`](crate::Page) & [`Database`](crate::Database).
20///
21/// Refer to:
22/// - [Notion JSON conventions](https://developers.notion.com/reference/intro#json-conventions)
23/// - [Block object](https://developers.notion.com/reference/block)
24/// - [Page object](https://developers.notion.com/reference/page)
25/// - [Database object](https://developers.notion.com/reference/database)
26#[serde_as]
27#[derive(Serialize, Deserialize, Debug, Clone)]
28pub struct ObjectCommon {
29    // #[serde_as(as = "DisplayFromStr")]
30    // pub object: ObjectType,
31    pub id: String,
32    pub parent: Parent,
33
34    pub created_time: DateTime<Utc>,
35    pub created_by: User,
36    pub last_edited_time: DateTime<Utc>,
37    pub last_edited_by: User,
38
39    pub archived: bool,
40    pub in_trash: bool,
41}
42
43impl ObjectCommon {
44    pub fn parent_type(&self) -> ParentType {
45        self.parent.r#type()
46    }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum ObjectType {
52    Block,
53    Page,
54    Database,
55    User,
56    Comment,
57    List,
58}
59
60impl Display for ObjectType {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        let s = match self {
63            ObjectType::Block => "block",
64            ObjectType::Page => "page",
65            ObjectType::Database => "database",
66            ObjectType::User => "user",
67            ObjectType::Comment => "comment",
68            ObjectType::List => "list",
69        };
70        s.fmt(f)
71    }
72}
73
74#[derive(Debug, Error)]
75#[error("UnsupportObjectError({0})")]
76pub struct UnsupportObjectError(String);
77
78#[allow(unused)]
79#[derive(Deserialize, Debug, Clone)]
80pub struct ObjectList<T> {
81    object: ObjectType, // should be "list"
82    pub results: Vec<T>,
83
84    #[serde(rename = "type")]
85    ttype: String,
86
87    #[serde(skip)]
88    pub start_index: usize,
89
90    #[serde(flatten)]
91    next_page_info: NextPageInfo,
92}
93
94/// See: [Pagination](https://developers.notion.com/reference/intro#pagination)
95#[derive(Deserialize, Debug, Clone)]
96struct NextPageInfo {
97    next_cursor: Option<String>,
98    has_more: bool,
99}
100
101pub trait NextCursor {
102    fn next_cursor(&self) -> Option<&str>;
103}
104
105impl<T> NextCursor for ObjectList<T> {
106    fn next_cursor(&self) -> Option<&str> {
107        if !self.next_page_info.has_more {
108            None
109        } else {
110            self.next_page_info.next_cursor.as_deref()
111        }
112    }
113}
114
115/// Refer to: [Parent object](https://developers.notion.com/reference/parent-object)
116#[derive(Serialize, Deserialize, Debug, Clone)]
117#[serde(untagged)]
118pub enum Parent {
119    Block { block_id: String },
120    Page { page_id: String },
121    Database { database_id: String },
122    Workspace { workspace: MustBe!(true) },
123}
124
125impl Parent {
126    pub fn id(&self) -> &str {
127        match self {
128            Parent::Block { block_id } => block_id,
129            Parent::Page { page_id } => page_id,
130            Parent::Database { database_id } => database_id,
131            Parent::Workspace { workspace: _ } => "workspace",
132        }
133    }
134
135    pub fn r#type(&self) -> ParentType {
136        match self {
137            Parent::Block { block_id: _ } => ParentType::BlockId,
138            Parent::Page { page_id: _ } => ParentType::PageId,
139            Parent::Database { database_id: _ } => ParentType::DatabaseId,
140            Parent::Workspace { workspace: _ } => ParentType::Workspace,
141        }
142    }
143
144    pub fn workspace() -> Self {
145        Self::Workspace {
146            workspace: MustBe!(true),
147        }
148    }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum ParentType {
154    DatabaseId,
155    PageId,
156    BlockId,
157    Workspace,
158}
159
160impl Display for ParentType {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        let s = serde_json::to_string(self).unwrap();
163        s.unquotes().fmt(f)
164    }
165}
166
167#[derive(Debug, PartialEq, Eq)]
168pub struct ImpossibleParseError;
169
170impl Display for ImpossibleParseError {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        "ImpossibleParseError".fmt(f)
173    }
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct JsonObject {
178    #[serde(flatten)]
179    pub map: BTreeMap<String, serde_json::Value>,
180}
181
182impl Display for JsonObject {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        let s = serde_json::to_string(self).unwrap();
185        s.unquotes().fmt(f)
186    }
187}