notion_into_sqlite/
notion_pages.rs1use crate::json_util::{dig_json, JsonKey};
2use crate::notion_database::{NotionDatabaseSchema, NotionPropertyType};
3use anyhow::{anyhow, Result};
4use rusqlite::ToSql;
5use serde_json::{Map, Value};
6use std::collections::HashMap;
7
8#[derive(Debug, PartialEq)]
9pub enum NotionPropertyValue {
10 Text(String),
11 Number(f64),
12 Json(Value),
13 Boolean(bool),
14}
15impl ToSql for NotionPropertyValue {
16 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
17 match self {
18 NotionPropertyValue::Text(value) => value.to_sql(),
19 NotionPropertyValue::Number(value) => value.to_sql(),
20 NotionPropertyValue::Json(value) => Ok(rusqlite::types::ToSqlOutput::from(
21 serde_json::to_string(value).unwrap(),
22 )),
23 NotionPropertyValue::Boolean(value) => value.to_sql(),
24 }
25 }
26}
27
28#[derive(Debug)]
29pub struct NotionPage {
30 pub id: String,
31 pub properties: HashMap<String, NotionPropertyValue>,
32 pub url: String,
33 pub created_time: String,
34 pub created_by: Value,
35 pub last_edited_time: String,
36 pub last_edited_by: Value,
37 pub archived: bool,
38}
39
40#[allow(non_snake_case)]
41#[derive(Debug)]
42struct NotionPageBuilder<'a> {
43 schema: &'a NotionDatabaseSchema,
44 TITLE_JSON_PATH: Vec<JsonKey<'a>>,
45 SELECT_JSON_PATH: Vec<JsonKey<'a>>,
46}
47impl NotionPageBuilder<'_> {
48 fn new(schema: &NotionDatabaseSchema) -> NotionPageBuilder<'_> {
49 NotionPageBuilder {
50 schema,
51 TITLE_JSON_PATH: vec!["title".into(), 0.into(), "plain_text".into()],
52 SELECT_JSON_PATH: vec!["select".into(), "name".into()],
53 }
54 }
55
56 fn from(&self, json_entry: &Map<String, Value>) -> Option<NotionPage> {
57 let id = json_entry.get("id")?.as_str()?.to_string();
58
59 let url = json_entry.get("url")?.as_str()?.to_string();
60 let created_time = json_entry.get("created_time")?.as_str()?.to_owned();
61 let created_by = json_entry.get("created_by")?.clone();
62 let last_edited_time = json_entry.get("last_edited_time")?.as_str()?.to_owned();
63 let last_edited_by = json_entry.get("last_edited_by")?.clone();
64 let archived = json_entry.get("archived")?.as_bool()?;
65
66 let properties_object = json_entry.get("properties")?.as_object()?;
67 let properties = properties_object
68 .iter()
69 .filter_map(|(key, property)| {
70 let property_schema = self.schema.properties.get(key)?;
71 let value: NotionPropertyValue = match property_schema.property_type {
72 NotionPropertyType::RichText => {
74 NotionPropertyValue::Json(property.get("rich_text")?.clone())
75 }
76 NotionPropertyType::Number => {
77 NotionPropertyValue::Number(property.get("number")?.as_f64()?)
78 }
79 NotionPropertyType::Select => NotionPropertyValue::Text(
80 dig_json(property, &self.SELECT_JSON_PATH)?
81 .as_str()?
82 .to_string(),
83 ),
84 NotionPropertyType::Title => NotionPropertyValue::Text(
85 dig_json(property, &self.TITLE_JSON_PATH)?
86 .as_str()?
87 .to_string(),
88 ),
89 NotionPropertyType::Checkbox => {
90 NotionPropertyValue::Boolean(property.get("checkbox")?.as_bool()?)
91 }
92 NotionPropertyType::Url => {
93 NotionPropertyValue::Text(property.get("url")?.as_str()?.to_string())
94 }
95 NotionPropertyType::Email => {
96 NotionPropertyValue::Text(property.get("email")?.as_str()?.to_string())
97 }
98 NotionPropertyType::PhoneNumber => NotionPropertyValue::Text(
99 property.get("phone_number")?.as_str()?.to_string(),
100 ),
101 NotionPropertyType::CreatedTime => NotionPropertyValue::Text(
102 property.get("created_time")?.as_str()?.to_string(),
103 ),
104 NotionPropertyType::LastEditedTime => NotionPropertyValue::Text(
105 property.get("last_edited_time")?.as_str()?.to_string(),
106 ),
107 NotionPropertyType::Other => NotionPropertyValue::Json(property.clone()),
108 _ => NotionPropertyValue::Json(
109 property.get(&property_schema.property_raw_type)?.clone(),
110 ),
111 };
112 Some((key.to_string(), value))
113 })
114 .collect::<HashMap<String, NotionPropertyValue>>();
115
116 Some(NotionPage {
117 id,
118 properties,
119 url,
120 created_time,
121 created_by,
122 last_edited_by,
123 last_edited_time,
124 archived,
125 })
126 }
127}
128
129pub fn parse_notion_page_list(
130 schema: &NotionDatabaseSchema,
131 query_resp: &Value,
132) -> Result<(Vec<NotionPage>, Option<String>)> {
133 validate_object_type(query_resp)?;
134
135 let next_cursor = get_next_cursor(query_resp);
136
137 let results_json_keys = vec![JsonKey::String("results")];
138 let results = dig_json(query_resp, &results_json_keys)
139 .and_then(|results| results.as_array())
140 .map(|results| {
141 results
142 .iter()
143 .filter_map(|r| r.as_object())
144 .collect::<Vec<_>>()
145 })
146 .ok_or_else(|| anyhow!(r#"It must have "results" as arrray of objects."#))?;
147
148 let page_builder = NotionPageBuilder::new(schema);
149 let pages: Vec<NotionPage> = results
150 .iter()
151 .filter_map(|&result| page_builder.from(result))
152 .collect::<Vec<_>>();
153
154 Ok((pages, next_cursor))
155}
156
157fn validate_object_type(query_resp: &Value) -> Result<()> {
158 let json_keys = vec![JsonKey::String("object")];
159 let object_field = dig_json(query_resp, &json_keys)
160 .and_then(|o| o.as_str())
161 .ok_or_else(|| anyhow!(r#"It must have `"object": "list"`."#.to_string()))?;
162
163 if object_field == "list" {
164 Ok(())
165 } else {
166 Err(anyhow!(
167 r#"It must have `"object": "list"`, but was "{}""#,
168 object_field
169 ))
170 }
171}
172
173fn get_next_cursor(query_resp: &Value) -> Option<String> {
174 let json_keys: Vec<JsonKey> = vec!["next_cursor".into()];
175 Some(dig_json(query_resp, &json_keys)?.as_str()?.to_string())
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_validate_object_type() {
184 let data = r#"
185 {
186 "object": "list"
187 }
188 "#;
189 let json = serde_json::from_str(data).unwrap();
190 assert!(validate_object_type(&json).is_ok());
191
192 let data = r#"
193 {
194 "object": "xxx"
195 }
196 "#;
197 let json = serde_json::from_str(data).unwrap();
198 assert!(validate_object_type(&json).is_err());
199
200 let data = r#"
201 {}
202 "#;
203 let json = serde_json::from_str(data).unwrap();
204 assert!(validate_object_type(&json).is_err());
205 }
206}