1pub mod block;
2pub mod error;
3pub mod paging;
4pub mod properties;
5pub mod search;
6#[cfg(test)]
7mod tests;
8pub mod text;
9pub mod users;
10
11use crate::models::properties::{PropertyConfiguration, PropertyValue};
12use crate::models::text::RichText;
13use crate::Error;
14use block::ExternalFileObject;
15use serde::{Deserialize, Serialize};
16use serde_json::Value;
17use std::collections::HashMap;
18
19use crate::ids::{AsIdentifier, DatabaseId, PageId};
20use crate::models::block::{Block, CreateBlock, FileObject};
21use crate::models::error::ErrorResponse;
22use crate::models::paging::PagingCursor;
23use crate::models::users::User;
24pub use chrono::{DateTime, Utc};
25pub use serde_json::value::Number;
26
27#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
28#[serde(rename_all = "snake_case")]
29enum ObjectType {
30 Database,
31 List,
32}
33
34#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
37pub struct Database {
38 pub id: DatabaseId,
40 pub created_time: DateTime<Utc>,
42 pub last_edited_time: DateTime<Utc>,
44 pub title: Vec<RichText>,
46 pub icon: Option<IconObject>,
54 pub properties: HashMap<String, PropertyConfiguration>,
55}
56
57#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
58#[serde(tag = "type")]
59#[serde(rename_all = "snake_case")]
60pub enum IconObject {
61 File {
62 #[serde(flatten)]
63 file: FileObject,
64 },
65 External {
66 external: ExternalFileObject,
67 },
68 Emoji {
69 emoji: String,
70 },
71}
72
73impl AsIdentifier<DatabaseId> for Database {
74 fn as_id(&self) -> &DatabaseId {
75 &self.id
76 }
77}
78
79impl Database {
80 pub fn title_plain_text(&self) -> String {
81 self.title
82 .iter()
83 .flat_map(|rich_text| rich_text.plain_text().chars())
84 .collect()
85 }
86}
87
88#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)]
90pub struct ListResponse<T> {
91 pub results: Vec<T>,
92 pub next_cursor: Option<PagingCursor>,
93 pub has_more: bool,
94}
95
96impl<T> ListResponse<T> {
97 pub fn results(&self) -> &[T] {
98 &self.results
99 }
100}
101
102impl ListResponse<Object> {
103 pub fn only_databases(self) -> ListResponse<Database> {
104 let databases = self
105 .results
106 .into_iter()
107 .filter_map(|object| match object {
108 Object::Database { database } => Some(database),
109 _ => None,
110 })
111 .collect();
112
113 ListResponse {
114 results: databases,
115 has_more: self.has_more,
116 next_cursor: self.next_cursor,
117 }
118 }
119
120 pub(crate) fn expect_databases(self) -> Result<ListResponse<Database>, crate::Error> {
121 let databases: Result<Vec<_>, _> = self
122 .results
123 .into_iter()
124 .map(|object| match object {
125 Object::Database { database } => Ok(database),
126 response => Err(Error::UnexpectedResponse { response }),
127 })
128 .collect();
129
130 Ok(ListResponse {
131 results: databases?,
132 has_more: self.has_more,
133 next_cursor: self.next_cursor,
134 })
135 }
136
137 pub(crate) fn expect_pages(self) -> Result<ListResponse<Page>, crate::Error> {
138 let items: Result<Vec<_>, _> = self
139 .results
140 .into_iter()
141 .map(|object| match object {
142 Object::Page { page } => Ok(page),
143 response => Err(Error::UnexpectedResponse { response }),
144 })
145 .collect();
146
147 Ok(ListResponse {
148 results: items?,
149 has_more: self.has_more,
150 next_cursor: self.next_cursor,
151 })
152 }
153
154 pub(crate) fn expect_blocks(self) -> Result<ListResponse<Block>, crate::Error> {
155 let items: Result<Vec<_>, _> = self
156 .results
157 .into_iter()
158 .map(|object| match object {
159 Object::Block { block } => Ok(block),
160 response => Err(Error::UnexpectedResponse { response }),
161 })
162 .collect();
163
164 Ok(ListResponse {
165 results: items?,
166 has_more: self.has_more,
167 next_cursor: self.next_cursor,
168 })
169 }
170}
171
172#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
173#[serde(tag = "type")]
174#[serde(rename_all = "snake_case")]
175pub enum Parent {
176 #[serde(rename = "database_id")]
177 Database {
178 database_id: DatabaseId,
179 },
180 #[serde(rename = "page_id")]
181 Page {
182 page_id: PageId,
183 },
184 Workspace,
185}
186
187#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
188pub struct Properties {
189 #[serde(flatten)]
190 pub properties: HashMap<String, PropertyValue>,
191}
192
193impl Properties {
194 pub fn title(&self) -> Option<String> {
195 self.properties.values().find_map(|p| match p {
196 PropertyValue::Title { title, .. } => {
197 Some(title.iter().map(|t| t.plain_text()).collect())
198 }
199 _ => None,
200 })
201 }
202}
203
204#[derive(Serialize, Debug, Eq, PartialEq)]
205pub struct PageCreateRequest {
206 pub parent: Parent,
207 pub properties: Properties,
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub children: Option<Vec<CreateBlock>>,
210}
211
212#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
213pub struct Page {
214 pub id: PageId,
215 pub created_time: DateTime<Utc>,
217 pub last_edited_time: DateTime<Utc>,
219 pub archived: bool,
221 pub properties: Properties,
222 pub icon: Option<IconObject>,
223 pub parent: Parent,
224}
225
226impl Page {
227 pub fn title(&self) -> Option<String> {
228 self.properties.title()
229 }
230}
231
232impl AsIdentifier<PageId> for Page {
233 fn as_id(&self) -> &PageId {
234 &self.id
235 }
236}
237
238#[derive(Eq, Serialize, Deserialize, Clone, Debug, PartialEq)]
239#[serde(tag = "object")]
240#[serde(rename_all = "snake_case")]
241pub enum Object {
242 Block {
243 #[serde(flatten)]
244 block: Block,
245 },
246 Database {
247 #[serde(flatten)]
248 database: Database,
249 },
250 Page {
251 #[serde(flatten)]
252 page: Page,
253 },
254 List {
255 #[serde(flatten)]
256 list: ListResponse<Object>,
257 },
258 User {
259 #[serde(flatten)]
260 user: User,
261 },
262 Error {
263 #[serde(flatten)]
264 error: ErrorResponse,
265 },
266}
267
268impl Object {
269 pub fn is_database(&self) -> bool {
270 matches!(self, Object::Database { .. })
271 }
272}