feat: implemented "*Property::Unsupported" for unrecognized property types

This commit is contained in:
Bram Dingelstad 2023-10-17 21:12:42 +02:00
parent a692f6884a
commit 3e5872f003
2 changed files with 127 additions and 22 deletions

View file

@ -14,6 +14,7 @@ base64 = "0.21.0"
chrono = "0.4.23"
futures-core = "0.3.28"
lazy_static = "1.4.0"
log = "0.4.20"
regex = "1.7.1"
reqwest = { version = "0.11.14", features = ["json"] }
serde = { version = "1.0.152", features = ["derive"] }

View file

@ -6,7 +6,8 @@ use lazy_static::lazy_static;
use regex::Regex;
#[cfg(feature = "request")]
use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use serde::de::Error as SerdeError;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::json;
use serde_json::Value;
@ -34,6 +35,7 @@ pub enum Error {
Deserialization(serde_json::Error, Option<Value>),
Header(reqwest::header::InvalidHeaderValue),
ChronoParse(chrono::ParseError),
UnexpectedType,
}
impl std::fmt::Display for Error {
@ -74,16 +76,8 @@ async fn try_to_parse_response<T: std::fmt::Debug + for<'de> serde::Deserialize<
match serde_json::from_str::<T>(&text) {
Ok(value) => Ok(value),
Err(error) => match serde_json::from_str::<Value>(&text) {
Ok(body) => {
println!("Error: {error:#?}\n\nBody: {body:#?}");
Err(Error::Deserialization(error, None))
}
_ => {
println!("Error: {error:#?}\n\nBody: {text}");
Err(Error::Deserialization(error, None))
}
Ok(body) => Err(Error::Deserialization(error, Some(body))),
_ => Err(Error::Deserialization(error, Some(Value::String(text)))),
},
}
}
@ -764,6 +758,8 @@ pub struct Database {
pub id: String,
pub title: Vec<RichText>,
pub description: Vec<RichText>,
#[serde(deserialize_with = "deserialize_database_properties")]
pub properties: HashMap<String, DatabaseProperty>,
pub url: String,
@ -872,9 +868,7 @@ pub enum DatabaseProperty {
name: String,
},
// TODO: Implement Unsupported(Value)
#[serde(other)]
Unsupported,
Unsupported(Value),
}
impl DatabaseProperty {
@ -902,7 +896,7 @@ impl DatabaseProperty {
| Title { id, .. }
| Url { id, .. } => Some(id.to_owned()),
Unsupported => None,
Unsupported(..) => None,
}
}
@ -930,11 +924,36 @@ impl DatabaseProperty {
| Title { name, .. }
| Url { name, .. } => Some(name.to_owned()),
Unsupported => None,
Unsupported(..) => None,
}
}
}
fn deserialize_database_properties<'de, D>(
deserializer: D,
) -> std::result::Result<HashMap<String, DatabaseProperty>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Value::deserialize(deserializer)?
.as_object_mut()
.ok_or(Error::UnexpectedType)
.map_err(D::Error::custom)?
.into_iter()
.map(|(key, value)| {
(
key.to_owned(),
serde_json::from_value::<DatabaseProperty>(value.to_owned()).unwrap_or_else(|error| {
log::warn!(
"Could not parse value because of error, defaulting to Unsupported:\n= ERROR:\n{error:#?}\n= VALUE:\n{value:#?}\n---"
);
DatabaseProperty::Unsupported(value.to_owned())
}),
)
})
.collect::<HashMap<String, DatabaseProperty>>())
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Number {
// TODO: Implement NumberFormat
@ -970,6 +989,7 @@ pub struct Page {
pub cover: Option<File>,
pub icon: Option<Icon>,
#[serde(deserialize_with = "deserialize_properties")]
pub properties: HashMap<String, Property>,
pub archived: bool,
@ -1025,6 +1045,7 @@ pub enum Property {
},
Formula {
id: String,
name: Option<String>,
formula: Formula,
},
LastEditedBy {
@ -1054,7 +1075,7 @@ pub enum Property {
},
Relation {
id: String,
relation: Vec<Relation>,
// relation: Vec<Relation>,
},
Rollup {
id: String,
@ -1074,10 +1095,14 @@ pub enum Property {
id: String,
url: Option<String>,
},
Verification {
id: String, // TODO: Implement
},
UniqueId {
id: String,
},
// TODO: Implement Unsupported(Value)
#[serde(other)]
Unsupported,
Unsupported(Value),
}
impl Property {
@ -1104,13 +1129,92 @@ impl Property {
| Select { id, .. }
| Status { id, .. }
| Url { id, .. }
| Formula { id, .. } => Some(id.to_owned()),
| Formula { id, .. }
| Verification { id, .. }
| UniqueId { id, .. } => Some(id.to_owned()),
Unsupported => None,
Unsupported(_) => None,
}
}
}
fn deserialize_properties<'de, D>(
deserializer: D,
) -> std::result::Result<HashMap<String, Property>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Value::deserialize(deserializer)?
.as_object_mut()
.ok_or(Error::UnexpectedType)
.map_err(D::Error::custom)?
.into_iter()
.map(|(key, value)| {
if let Value::Object(ref mut object) = value {
// Notion sometimes sends an empty object when it means "null", so we gotta do it's homework
for value in object.values_mut() {
if value == &mut json!({}) {
*value = Value::Null
}
}
// Correcting missing values
for (key, value) in object.iter_mut() {
match key.as_ref() {
// Notion forgets to set the formula type, so we're doing it's homework
"formula" => {
if let Value::Object(object) = value {
if let None = object.get("type") {
object.insert("type".to_owned(), json!("string"));
}
}
}
// Notion sometimes just sets title to an empty object? Guess we have to set it to something?
"title" => {
if let Value::Null = value {
*value = json!([{
"type": "text",
"text": {
"content": "Unknown title",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Unknown title",
"href": null
}])
}
}
// Notion sometimes forget to set a value for a checkbox??
"checkbox" => {
if let Value::Null = value {
*value = json!(false)
}
}
_ => {}
};
}
}
(
key.to_owned(),
serde_json::from_value::<Property>(value.to_owned()).unwrap_or_else(|error| {
log::warn!(
"Could not parse value because of error, defaulting to Unsupported:\n= ERROR:\n{error:#?}\n= VALUE:\n{value:#?}\n---"
);
Property::Unsupported(value.to_owned())
}),
)
})
.collect::<HashMap<String, Property>>())
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]