Compare commits
No commits in common. "main" and "feat-switch-to-surf-instead-of-reqwest" have entirely different histories.
main
...
feat-switc
5 changed files with 36 additions and 266 deletions
0
.gitignore
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
3
Cargo.toml
Executable file → Normal file
3
Cargo.toml
Executable file → Normal file
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "notion-client"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
|
|
@ -20,4 +20,3 @@ regex = "1.7.1"
|
|||
serde = { version = "^1.0", features = ["derive"], default-features = false }
|
||||
serde_json = { version = "^1.0", features = ["raw_value"], default-features = false }
|
||||
surf = { version = "2.3.2", default-features = false }
|
||||
uuid = "1.16.0"
|
||||
|
|
|
|||
0
LICENSE
Executable file → Normal file
0
LICENSE
Executable file → Normal file
0
README.md
Executable file → Normal file
0
README.md
Executable file → Normal file
299
src/lib.rs
Executable file → Normal file
299
src/lib.rs
Executable file → Normal file
|
|
@ -1,12 +1,12 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, NaiveTime, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::de::Error as SerdeError;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
use surf::http::StatusCode;
|
||||
|
||||
use futures_core::future::BoxFuture;
|
||||
|
|
@ -276,7 +276,6 @@ pub struct Databases<'a> {
|
|||
}
|
||||
|
||||
impl Databases<'_> {
|
||||
// FIXME: This call can have Databases mixed in with the Page's, needs an intermediary
|
||||
pub async fn query<'a>(
|
||||
&self,
|
||||
options: DatabaseQueryOptions<'a>,
|
||||
|
|
@ -389,7 +388,9 @@ pub struct Users<'a> {
|
|||
|
||||
impl Users<'_> {
|
||||
pub async fn get(&self) -> Result<QueryResponse<User>> {
|
||||
let request = self.http_client.get("https://api.notion.com/v1/users");
|
||||
let url = "https://api.notion.com/v1/users".to_owned();
|
||||
|
||||
let request = self.http_client.get(&url);
|
||||
|
||||
let mut response = (self.request_handler)(request).await?;
|
||||
|
||||
|
|
@ -701,7 +702,7 @@ pub struct ChildDatabase {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct User {
|
||||
pub id: uuid::Uuid,
|
||||
pub id: String,
|
||||
pub name: Option<String>,
|
||||
pub person: Option<Person>,
|
||||
pub avatar_url: Option<String>,
|
||||
|
|
@ -802,7 +803,7 @@ pub enum DatabaseProperty {
|
|||
Relation {
|
||||
id: String,
|
||||
name: String,
|
||||
relation: DatabaseRelation,
|
||||
// relation: Relation,
|
||||
},
|
||||
RichText {
|
||||
id: String,
|
||||
|
|
@ -811,11 +812,7 @@ pub enum DatabaseProperty {
|
|||
Rollup {
|
||||
id: String,
|
||||
name: String,
|
||||
function: RollupFunction,
|
||||
relation_property_id: String,
|
||||
relation_property_name: String,
|
||||
rollup_property_id: String,
|
||||
rollup_property_name: String,
|
||||
// TODO: Implement Rollup
|
||||
},
|
||||
Select {
|
||||
id: String,
|
||||
|
|
@ -825,8 +822,7 @@ pub enum DatabaseProperty {
|
|||
Status {
|
||||
id: String,
|
||||
name: String,
|
||||
// TODO: add "groups"
|
||||
options: DatabaseSelectOptions,
|
||||
// TODO: Implement Status
|
||||
},
|
||||
Title {
|
||||
id: String,
|
||||
|
|
@ -845,7 +841,7 @@ pub enum DatabaseProperty {
|
|||
}
|
||||
|
||||
impl DatabaseProperty {
|
||||
pub fn id(&self) -> Option<&str> {
|
||||
pub fn id(&self) -> Option<String> {
|
||||
use DatabaseProperty::*;
|
||||
|
||||
match self {
|
||||
|
|
@ -868,13 +864,13 @@ impl DatabaseProperty {
|
|||
| Status { id, .. }
|
||||
| Title { id, .. }
|
||||
| Button { id, .. }
|
||||
| Url { id, .. } => Some(id),
|
||||
| Url { id, .. } => Some(id.to_owned()),
|
||||
|
||||
Unsupported(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
use DatabaseProperty::*;
|
||||
|
||||
match self {
|
||||
|
|
@ -897,7 +893,7 @@ impl DatabaseProperty {
|
|||
| Status { name, .. }
|
||||
| Button { name, .. }
|
||||
| Title { name, .. }
|
||||
| Url { name, .. } => Some(name),
|
||||
| Url { name, .. } => Some(name.to_owned()),
|
||||
|
||||
Unsupported(..) => None,
|
||||
}
|
||||
|
|
@ -921,7 +917,7 @@ where
|
|||
|
||||
serde_json::from_value::<DatabaseProperty>(value.to_owned()).unwrap_or_else(|error| {
|
||||
log::warn!(
|
||||
"Could not parse DatabaseProperty value because of error, defaulting to DatabaseProperty::Unsupported:\n= ERROR:\n{error:?}\n= JSON:\n{:?}\n---",
|
||||
"Could not parse value because of error, defaulting to DatabaseProperty::Unsupported:\n= ERROR:\n{error:?}\n= JSON:\n{:?}\n---",
|
||||
serde_json::to_string_pretty(&value).expect("to pretty print the database property error")
|
||||
);
|
||||
DatabaseProperty::Unsupported(value.to_owned())
|
||||
|
|
@ -933,179 +929,16 @@ where
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Number {
|
||||
pub format: NumberFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NumberFormat {
|
||||
ArgentinePeso,
|
||||
Baht,
|
||||
AustralianDollar,
|
||||
CanadianDollar,
|
||||
ChileanPeso,
|
||||
ColombianPeso,
|
||||
DanishKrone,
|
||||
Dirham,
|
||||
Dollar,
|
||||
Euro,
|
||||
Forint,
|
||||
Franc,
|
||||
HongKongDollar,
|
||||
Koruna,
|
||||
Krona,
|
||||
Leu,
|
||||
Lira,
|
||||
MexicanPeso,
|
||||
NewTaiwanDollar,
|
||||
NewZealandDollar,
|
||||
NorwegianKrone,
|
||||
Number,
|
||||
NumberWithCommas,
|
||||
Percent,
|
||||
PhilippinePeso,
|
||||
Pound,
|
||||
PeruvianSol,
|
||||
Rand,
|
||||
Real,
|
||||
Ringgit,
|
||||
Riyal,
|
||||
Ruble,
|
||||
Rupee,
|
||||
Rupiah,
|
||||
Shekel,
|
||||
SingaporeDollar,
|
||||
UruguayanPeso,
|
||||
Yen,
|
||||
Yuan,
|
||||
Won,
|
||||
Zloty,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct DatabaseRelation {
|
||||
database_id: String,
|
||||
synced_property_name: String,
|
||||
synced_property_id: String,
|
||||
// TODO: Implement NumberFormat
|
||||
// pub format: NumberFormat
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Relation {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
// TODO: Implement all enums here
|
||||
pub enum Rollup {
|
||||
Array {
|
||||
function: RollupFunction,
|
||||
array: Vec<RollupProperty>,
|
||||
},
|
||||
Date,
|
||||
Incomplete,
|
||||
Number,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RollupFunction {
|
||||
Average,
|
||||
Checked,
|
||||
CountPerGroup,
|
||||
Count,
|
||||
CountValues,
|
||||
DateRange,
|
||||
EarliestDate,
|
||||
Empty,
|
||||
LatestDate,
|
||||
Max,
|
||||
Median,
|
||||
Min,
|
||||
NotEmpty,
|
||||
PercentChecked,
|
||||
PercentEmpty,
|
||||
PercentNotEmpty,
|
||||
PercentPerGroup,
|
||||
PercentUnchecked,
|
||||
Range,
|
||||
Unchecked,
|
||||
Unique,
|
||||
ShowOriginal,
|
||||
ShowUnique,
|
||||
Sum,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RollupProperty {
|
||||
Checkbox {
|
||||
checkbox: bool,
|
||||
},
|
||||
CreatedBy {},
|
||||
CreatedTime {
|
||||
created_time: DateValue,
|
||||
},
|
||||
Date {
|
||||
date: Option<Date>,
|
||||
},
|
||||
Email {
|
||||
email: Option<String>,
|
||||
},
|
||||
Files {
|
||||
files: Vec<File>,
|
||||
},
|
||||
Formula {
|
||||
name: Option<String>,
|
||||
formula: Formula,
|
||||
},
|
||||
LastEditedBy {
|
||||
last_edited_by: User,
|
||||
},
|
||||
LastEditedTime {
|
||||
last_edited_time: DateValue,
|
||||
},
|
||||
Select {
|
||||
select: Option<SelectOption>,
|
||||
},
|
||||
MultiSelect {
|
||||
multi_select: Vec<SelectOption>,
|
||||
},
|
||||
Number {
|
||||
number: Option<f32>,
|
||||
},
|
||||
People {},
|
||||
PhoneNumber {},
|
||||
Relation {
|
||||
relation: Vec<Relation>,
|
||||
},
|
||||
Rollup {
|
||||
rollup: Rollup,
|
||||
},
|
||||
RichText {
|
||||
rich_text: Vec<RichText>,
|
||||
},
|
||||
Status {
|
||||
status: Option<SelectOption>,
|
||||
},
|
||||
Title {
|
||||
title: Vec<RichText>,
|
||||
},
|
||||
Url {
|
||||
url: Option<String>,
|
||||
},
|
||||
Verification {
|
||||
state: VerificationState,
|
||||
verified_by: Option<User>,
|
||||
date: Option<Date>,
|
||||
},
|
||||
UniqueId {},
|
||||
Button {},
|
||||
|
||||
Unsupported(Value),
|
||||
// #[serde(alias = "database_id")]
|
||||
// id: String,
|
||||
// synced_property_name: String,
|
||||
// synced_property_id: String,
|
||||
}
|
||||
|
||||
// TODO: Paginate all possible responses
|
||||
|
|
@ -1114,7 +947,6 @@ pub struct QueryResponse<T> {
|
|||
pub has_more: Option<bool>,
|
||||
pub next_cursor: Option<String>,
|
||||
pub results: Vec<T>,
|
||||
pub page_or_database: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
|
|
@ -1134,7 +966,6 @@ pub struct Page {
|
|||
pub properties: HashMap<String, Property>,
|
||||
|
||||
pub archived: bool,
|
||||
pub in_trash: bool,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
|
|
@ -1168,7 +999,6 @@ pub enum Property {
|
|||
},
|
||||
CreatedBy {
|
||||
id: String,
|
||||
created_by: User,
|
||||
},
|
||||
CreatedTime {
|
||||
id: String,
|
||||
|
|
@ -1193,8 +1023,7 @@ pub enum Property {
|
|||
},
|
||||
LastEditedBy {
|
||||
id: String,
|
||||
last_edited_by: User,
|
||||
},
|
||||
}, // TODO: Implement LastEditedBy
|
||||
LastEditedTime {
|
||||
id: String,
|
||||
last_edited_time: DateValue,
|
||||
|
|
@ -1213,29 +1042,24 @@ pub enum Property {
|
|||
},
|
||||
People {
|
||||
id: String,
|
||||
people: Vec<User>,
|
||||
},
|
||||
PhoneNumber {
|
||||
id: String,
|
||||
phone_number: Option<String>,
|
||||
},
|
||||
Relation {
|
||||
id: String,
|
||||
has_more: bool,
|
||||
relation: Vec<Relation>,
|
||||
// relation: Vec<Relation>,
|
||||
},
|
||||
Rollup {
|
||||
id: String,
|
||||
rollup: Rollup,
|
||||
},
|
||||
}, // TODO: Implement Rollup
|
||||
RichText {
|
||||
id: String,
|
||||
rich_text: Vec<RichText>,
|
||||
},
|
||||
Status {
|
||||
id: String,
|
||||
status: Option<SelectOption>,
|
||||
},
|
||||
}, // TODO: Implement Status
|
||||
Title {
|
||||
id: String,
|
||||
title: Vec<RichText>,
|
||||
|
|
@ -1245,12 +1069,10 @@ pub enum Property {
|
|||
url: Option<String>,
|
||||
},
|
||||
Verification {
|
||||
id: String,
|
||||
verification: Verification,
|
||||
id: String, // TODO: Implement
|
||||
},
|
||||
UniqueId {
|
||||
id: String,
|
||||
unique_id: UniqueId,
|
||||
},
|
||||
Button {
|
||||
id: String,
|
||||
|
|
@ -1260,7 +1082,7 @@ pub enum Property {
|
|||
}
|
||||
|
||||
impl Property {
|
||||
pub fn id(&self) -> Option<&str> {
|
||||
pub fn id(&self) -> Option<String> {
|
||||
use Property::*;
|
||||
|
||||
match self {
|
||||
|
|
@ -1286,7 +1108,7 @@ impl Property {
|
|||
| Formula { id, .. }
|
||||
| Verification { id, .. }
|
||||
| Button { id, .. }
|
||||
| UniqueId { id, .. } => Some(id),
|
||||
| UniqueId { id, .. } => Some(id.to_owned()),
|
||||
|
||||
Unsupported(_) => None,
|
||||
}
|
||||
|
|
@ -1305,7 +1127,7 @@ where
|
|||
.map_err(D::Error::custom)?
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
if let Value::Object(object) = 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!({}) {
|
||||
|
|
@ -1361,7 +1183,7 @@ where
|
|||
key.to_owned(),
|
||||
serde_json::from_value::<Property>(value.to_owned()).unwrap_or_else(|error| {
|
||||
log::warn!(
|
||||
"Could not parse Property value because of error, defaulting to Property::Unsupported:\n= ERROR:\n{error:#?}\n= JSON:\n{}\n---",
|
||||
"Could not parse value because of error, defaulting to Property::Unsupported:\n= ERROR:\n{error:#?}\n= JSON:\n{}\n---",
|
||||
serde_json::to_string_pretty(&value).expect("to pretty print Property errors")
|
||||
);
|
||||
Property::Unsupported(value.to_owned())
|
||||
|
|
@ -1371,27 +1193,6 @@ where
|
|||
.collect::<HashMap<String, Property>>())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum VerificationState {
|
||||
Verified,
|
||||
Unverified,
|
||||
Expired,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Verification {
|
||||
state: VerificationState,
|
||||
verified_by: Option<User>,
|
||||
date: Option<Date>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct UniqueId {
|
||||
number: i32,
|
||||
prefix: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
|
@ -1413,38 +1214,6 @@ pub enum Formula {
|
|||
Unsupported,
|
||||
}
|
||||
|
||||
impl ToString for Formula {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Formula::String {
|
||||
string: Some(string),
|
||||
..
|
||||
} => string.to_owned(),
|
||||
Formula::Number {
|
||||
number: Some(number),
|
||||
..
|
||||
} => format!("{number}"),
|
||||
Formula::Boolean {
|
||||
boolean: Some(boolean),
|
||||
..
|
||||
} => format!("{boolean}"),
|
||||
Formula::Date {
|
||||
date: Some(date), ..
|
||||
} => format!("{date}"),
|
||||
|
||||
Formula::Unsupported => {
|
||||
log::warn!("User tried stringifying unsupported formula");
|
||||
"Formula output unsupported".to_owned()
|
||||
}
|
||||
|
||||
Formula::Number { number: None, .. }
|
||||
| Formula::Date { date: None, .. }
|
||||
| Formula::Boolean { boolean: None, .. }
|
||||
| Formula::String { string: None, .. } => "".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct PartialUser {
|
||||
pub id: String,
|
||||
|
|
@ -1555,7 +1324,7 @@ pub struct PartialBlock {
|
|||
pub struct Date {
|
||||
pub start: DateValue,
|
||||
pub end: Option<DateValue>,
|
||||
// TODO: Implement for updating with timezone in the future?
|
||||
// TODO: Implement for setting
|
||||
pub time_zone: Option<String>,
|
||||
}
|
||||
|
||||
|
|
@ -1573,8 +1342,8 @@ impl std::fmt::Display for Date {
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub enum DateValue {
|
||||
Date(DateTime<Utc>),
|
||||
DateTime(DateTime<Utc>),
|
||||
Date(chrono::NaiveDate),
|
||||
}
|
||||
|
||||
impl TryFrom<String> for DateValue {
|
||||
|
|
@ -1583,7 +1352,9 @@ impl TryFrom<String> for DateValue {
|
|||
fn try_from(string: String) -> Result<DateValue> {
|
||||
// NOTE: is either ISO 8601 Date or assumed to be ISO 8601 DateTime
|
||||
let value = if ISO_8601_DATE.is_match(&string) {
|
||||
DateValue::Date(DateTime::parse_from_rfc3339(&format!("{string}T00:00:00Z"))?.into())
|
||||
DateValue::Date(
|
||||
DateTime::parse_from_rfc3339(&format!("{string}T00:00:00Z"))?.date_naive(),
|
||||
)
|
||||
} else {
|
||||
DateValue::DateTime(DateTime::parse_from_rfc3339(&string)?.with_timezone(&Utc))
|
||||
};
|
||||
|
|
@ -1601,7 +1372,7 @@ impl From<DateValue> for String {
|
|||
impl std::fmt::Display for DateValue {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let value = match self {
|
||||
DateValue::Date(date) => date.format("%Y-%m-%d").to_string(),
|
||||
DateValue::Date(date) => date.and_time(NaiveTime::MIN).and_utc().to_rfc3339(),
|
||||
DateValue::DateTime(date_time) => date_time.to_rfc3339(),
|
||||
};
|
||||
Ok(write!(formatter, "{}", value)?)
|
||||
|
|
|
|||
Loading…
Reference in a new issue