feat: fixed spec + added custom request handler
This commit is contained in:
parent
6f1cad9b89
commit
84a5096ec2
|
@ -6,8 +6,10 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1.68"
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.23"
|
||||||
|
futures-core = "0.3.28"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
||||||
reqwest = { version = "0.11.14", features = ["json"] }
|
reqwest = { version = "0.11.14", features = ["json"] }
|
||||||
|
|
273
src/lib.rs
273
src/lib.rs
|
@ -9,27 +9,36 @@ use lazy_static::lazy_static;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use reqwest::header::{HeaderMap, HeaderValue};
|
use reqwest::header::{HeaderMap, HeaderValue};
|
||||||
|
|
||||||
|
use futures_core::future::BoxFuture;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ISO_8601_DATE : Regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$")
|
static ref ISO_8601_DATE: Regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$")
|
||||||
.expect("ISO 8601 date regex to be parseable");
|
.expect("ISO 8601 date regex to be parseable");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add the ability to hack into the code or add queuing
|
// TODO: Add the ability to hack into the code or add queuing
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
pub type Callback = dyn Fn(&mut reqwest::RequestBuilder) -> BoxFuture<'_, std::result::Result<reqwest::Response, reqwest::Error>> + 'static + Send + Sync;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Http(reqwest::Error),
|
Http(reqwest::Error, Option<Value>),
|
||||||
Deserialization(serde_json::Error, Option<Value>),
|
Deserialization(serde_json::Error, Option<Value>),
|
||||||
Header(reqwest::header::InvalidHeaderValue),
|
Header(reqwest::header::InvalidHeaderValue),
|
||||||
ChronoParse(chrono::ParseError),
|
ChronoParse(chrono::ParseError),
|
||||||
NoSuchProperty(String)
|
NoSuchProperty(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
Ok(write!(formatter, "NotionError::{:?}", self)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<reqwest::Error> for Error {
|
impl From<reqwest::Error> for Error {
|
||||||
fn from(error: reqwest::Error) -> Self {
|
fn from(error: reqwest::Error) -> Self {
|
||||||
Error::Http(error)
|
Error::Http(error, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +66,31 @@ fn parse<T: for<'de> Deserialize<'de>>(key: &str, data: &Value) -> Result<T> {
|
||||||
Ok(
|
Ok(
|
||||||
serde_json::from_value::<T>(
|
serde_json::from_value::<T>(
|
||||||
data.get(key).ok_or_else(|| Error::NoSuchProperty(key.to_string()))?.clone()
|
data.get(key).ok_or_else(|| Error::NoSuchProperty(key.to_string()))?.clone()
|
||||||
)?
|
|
||||||
)
|
)
|
||||||
|
.map_err(|error| Error::Deserialization(error, Some(data.clone())))?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_to_parse_response<T: std::fmt::Debug + for<'de> serde::Deserialize<'de>>(response: reqwest::Response) -> Result<T> {
|
||||||
|
let text = response.text().await?;
|
||||||
|
|
||||||
|
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, Some(body)))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("Error: {error:#?}\n\nBody: {text}");
|
||||||
|
|
||||||
|
Err(Error::Deserialization(error, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOTION_VERSION: &str = "2022-06-28";
|
const NOTION_VERSION: &str = "2022-06-28";
|
||||||
|
@ -75,13 +107,7 @@ fn get_http_client(notion_api_key: &str) -> reqwest::Client {
|
||||||
.expect("to build a valid client out of notion_api_key")
|
.expect("to build a valid client out of notion_api_key")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub struct Client {
|
|
||||||
http_client: Arc<reqwest::Client>,
|
|
||||||
pub pages: Pages,
|
|
||||||
pub blocks: Blocks,
|
|
||||||
pub databases: Databases
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -99,31 +125,96 @@ pub struct SearchOptions<'a> {
|
||||||
pub page_size: Option<u32>
|
pub page_size: Option<u32>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Client {
|
#[derive(Default)]
|
||||||
pub fn new(notion_api_key: &'a str) -> Self {
|
pub struct ClientBuilder {
|
||||||
let http_client = Arc::from(get_http_client(notion_api_key));
|
api_key: Option<String>,
|
||||||
|
custom_request: Option<Arc<Callback>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientBuilder {
|
||||||
|
pub fn api_key(mut self, api_key: &str) -> Self {
|
||||||
|
self.api_key = Some(api_key.to_owned());
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn custom_request<F>(mut self, callback: F) -> Self
|
||||||
|
where
|
||||||
|
for<'c> F: Fn(&'c mut reqwest::RequestBuilder) -> BoxFuture<'c, std::result::Result<reqwest::Response, reqwest::Error>>
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync {
|
||||||
|
self.custom_request = Some(Arc::new(callback));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Client {
|
||||||
|
let notion_api_key = self.api_key
|
||||||
|
.expect("api_key to be set");
|
||||||
|
|
||||||
|
let request_handler = self.custom_request
|
||||||
|
.unwrap_or(
|
||||||
|
Arc::new(
|
||||||
|
|request_builder: &mut reqwest::RequestBuilder| Box::pin(async move {
|
||||||
|
let request = request_builder.try_clone()
|
||||||
|
.expect("non-stream body request clone to succeed");
|
||||||
|
|
||||||
|
request.send().await
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let http_client = Arc::from(get_http_client(¬ion_api_key));
|
||||||
|
|
||||||
Client {
|
Client {
|
||||||
http_client: http_client.clone(),
|
http_client: http_client.clone(),
|
||||||
pages: Pages { http_client: http_client.clone() },
|
request_handler: request_handler.clone(),
|
||||||
blocks: Blocks { http_client: http_client.clone() },
|
|
||||||
databases: Databases { http_client: http_client.clone() }
|
pages: Pages {
|
||||||
|
http_client: http_client.clone(),
|
||||||
|
request_handler: request_handler.clone()
|
||||||
|
},
|
||||||
|
blocks: Blocks {
|
||||||
|
http_client: http_client.clone(),
|
||||||
|
request_handler: request_handler.clone()
|
||||||
|
},
|
||||||
|
databases: Databases {
|
||||||
|
http_client: http_client.clone(),
|
||||||
|
request_handler: request_handler.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub struct Client {
|
||||||
|
http_client: Arc<reqwest::Client>,
|
||||||
|
request_handler: Arc<Callback>,
|
||||||
|
|
||||||
|
pub pages: Pages,
|
||||||
|
pub blocks: Blocks,
|
||||||
|
pub databases: Databases
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Client {
|
||||||
|
pub fn new() -> ClientBuilder {
|
||||||
|
ClientBuilder::default()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn search<'b, T: std::fmt::Debug + for<'de> serde::Deserialize<'de>>(self, options: SearchOptions<'b>) -> Result<QueryResponse<T>> {
|
pub async fn search<'b, T: std::fmt::Debug + for<'de> serde::Deserialize<'de>>(self, options: SearchOptions<'b>) -> Result<QueryResponse<T>> {
|
||||||
let response = self.http_client
|
let mut request = self.http_client
|
||||||
.post("https://api.notion.com/v1/search")
|
.post("https://api.notion.com/v1/search")
|
||||||
.json(&options)
|
.json(&options);
|
||||||
.send()
|
|
||||||
.await?;
|
let response = (self.request_handler)(&mut request).await?;
|
||||||
|
|
||||||
match response.error_for_status_ref() {
|
match response.error_for_status_ref() {
|
||||||
Ok(_) => Ok(response.json().await?),
|
Ok(_) => Ok(response.json().await?),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("Error: {error:#?}");
|
let body = response.json::<Value>().await?;
|
||||||
println!("Body: {:#?}", response.json::<Value>().await?);
|
Err(Error::Http(error, Some(body)))
|
||||||
Err(Error::Http(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,24 +226,24 @@ pub struct PageOptions<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pages {
|
pub struct Pages {
|
||||||
http_client: Arc<reqwest::Client>
|
http_client: Arc<reqwest::Client>,
|
||||||
|
request_handler: Arc<Callback>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pages {
|
impl Pages {
|
||||||
pub async fn retrieve<'a>(self, options: PageOptions<'a>) -> Result<Page> {
|
pub async fn retrieve<'a>(self, options: PageOptions<'a>) -> Result<Page> {
|
||||||
let url = format!("https://api.notion.com/v1/pages/{page_id}", page_id = options.page_id);
|
let url = format!("https://api.notion.com/v1/pages/{page_id}", page_id = options.page_id);
|
||||||
|
|
||||||
let response = self.http_client
|
let mut request = self.http_client
|
||||||
.get(url)
|
.get(url);
|
||||||
.send()
|
|
||||||
.await?;
|
let response = (self.request_handler)(&mut request).await?;
|
||||||
|
|
||||||
match response.error_for_status_ref() {
|
match response.error_for_status_ref() {
|
||||||
Ok(_) => Ok(response.json().await?),
|
Ok(_) => Ok(response.json().await?),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("Error: {error:#?}");
|
let body = response.json::<Value>().await?;
|
||||||
println!("Body: {:#?}", response.json::<Value>().await?);
|
Err(Error::Http(error, Some(body)))
|
||||||
Err(Error::Http(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,18 +251,21 @@ impl Pages {
|
||||||
|
|
||||||
pub struct Blocks {
|
pub struct Blocks {
|
||||||
http_client: Arc<reqwest::Client>,
|
http_client: Arc<reqwest::Client>,
|
||||||
|
request_handler: Arc<Callback>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Blocks {
|
impl Blocks {
|
||||||
pub fn children(&self) -> BlockChildren {
|
pub fn children(&self) -> BlockChildren {
|
||||||
BlockChildren {
|
BlockChildren {
|
||||||
http_client: self.http_client.clone()
|
http_client: self.http_client.clone(),
|
||||||
|
request_handler: self.request_handler.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockChildren {
|
pub struct BlockChildren {
|
||||||
http_client: Arc<reqwest::Client>,
|
http_client: Arc<reqwest::Client>,
|
||||||
|
request_handler: Arc<Callback>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockChildrenListOptions<'a> {
|
pub struct BlockChildrenListOptions<'a> {
|
||||||
|
@ -182,19 +276,18 @@ impl BlockChildren {
|
||||||
pub async fn list<'a>(self, options: BlockChildrenListOptions<'a>) -> Result<QueryResponse<Block>> {
|
pub async fn list<'a>(self, options: BlockChildrenListOptions<'a>) -> Result<QueryResponse<Block>> {
|
||||||
let url = format!("https://api.notion.com/v1/blocks/{block_id}/children", block_id = options.block_id);
|
let url = format!("https://api.notion.com/v1/blocks/{block_id}/children", block_id = options.block_id);
|
||||||
|
|
||||||
let request = self.http_client
|
let mut request = self.http_client
|
||||||
.get(&url)
|
.get(&url);
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
match request.error_for_status_ref() {
|
let response = (self.request_handler)(&mut request).await?;
|
||||||
|
|
||||||
|
match response.error_for_status_ref() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
Ok(request.json().await?)
|
Ok(response.json().await?)
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("Error: {error:#?}");
|
let body = response.json::<Value>().await?;
|
||||||
println!("Body: {:#?}", request.json::<Value>().await?);
|
Err(Error::Http(error, Some(body)))
|
||||||
Err(Error::Http(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,6 +295,7 @@ impl BlockChildren {
|
||||||
|
|
||||||
pub struct Databases {
|
pub struct Databases {
|
||||||
http_client: Arc<reqwest::Client>,
|
http_client: Arc<reqwest::Client>,
|
||||||
|
request_handler: Arc<Callback>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Databases {
|
impl Databases {
|
||||||
|
@ -215,16 +309,13 @@ impl Databases {
|
||||||
request = request.json(&json!({ "filter": filter }));
|
request = request.json(&json!({ "filter": filter }));
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = request
|
let response = (self.request_handler)(&mut request).await?;
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
match request.error_for_status_ref() {
|
match response.error_for_status_ref() {
|
||||||
Ok(_) => Ok(request.json().await?),
|
Ok(_) => try_to_parse_response(response).await,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("Error: {error:#?}");
|
let body = try_to_parse_response::<Value>(response).await?;
|
||||||
println!("Body: {:#?}", request.json::<Value>().await?);
|
Err(Error::Http(error, Some(body)))
|
||||||
Err(Error::Http(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -523,7 +614,7 @@ pub struct ChildDatabase {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: Option<String>,
|
||||||
pub person: Option<Person>,
|
pub person: Option<Person>,
|
||||||
pub avatar_url: Option<String>
|
pub avatar_url: Option<String>
|
||||||
}
|
}
|
||||||
|
@ -592,15 +683,14 @@ pub struct PartialUser {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(try_from = "Value")]
|
#[serde(try_from = "Value")]
|
||||||
pub struct Properties {
|
pub struct Properties {
|
||||||
pub map: HashMap<String, Property>
|
pub map: HashMap<String, Property>,
|
||||||
|
pub id_map: HashMap<String, Property>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Properties {
|
impl Properties {
|
||||||
pub fn get(&self, key: &str) -> Option<Property> {
|
pub fn get(&self, key: &str) -> Option<&Property> {
|
||||||
match self.map.get(key) {
|
self.map.get(key)
|
||||||
Some(property) => Some(property.to_owned()),
|
.or(self.id_map.get(key))
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keys(&self) -> Vec<String> {
|
pub fn keys(&self) -> Vec<String> {
|
||||||
|
@ -615,27 +705,30 @@ impl TryFrom<Value> for Properties {
|
||||||
|
|
||||||
fn try_from(data: Value) -> Result<Properties> {
|
fn try_from(data: Value) -> Result<Properties> {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
let mut id_map = HashMap::new();
|
||||||
|
|
||||||
for key in data.as_object().unwrap().keys() {
|
for key in data.as_object().unwrap().keys() {
|
||||||
map.insert(key.to_owned(), parse(key, &data)?);
|
let property: Property = parse(key, &data)?;
|
||||||
|
|
||||||
|
map.insert(key.to_owned(), property.clone());
|
||||||
|
id_map.insert(property.id.clone(), property);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Properties { map })
|
Ok(Properties { map, id_map })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(try_from = "Value")]
|
#[serde(try_from = "Value")]
|
||||||
pub struct DatabaseProperties {
|
pub struct DatabaseProperties {
|
||||||
pub map: HashMap<String, DatabaseProperty>
|
pub map: HashMap<String, DatabaseProperty>,
|
||||||
|
pub id_map: HashMap<String, DatabaseProperty>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseProperties {
|
impl DatabaseProperties {
|
||||||
pub fn get(&self, key: &str) -> Option<DatabaseProperty> {
|
pub fn get(&self, key: &str) -> Option<&DatabaseProperty> {
|
||||||
match self.map.get(key) {
|
self.map.get(key)
|
||||||
Some(property) => Some(property.to_owned()),
|
.or(self.id_map.get(key))
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keys(&self) -> Vec<String> {
|
pub fn keys(&self) -> Vec<String> {
|
||||||
|
@ -650,12 +743,16 @@ impl TryFrom<Value> for DatabaseProperties {
|
||||||
|
|
||||||
fn try_from(data: Value) -> Result<DatabaseProperties> {
|
fn try_from(data: Value) -> Result<DatabaseProperties> {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
let mut id_map = HashMap::new();
|
||||||
|
|
||||||
for key in data.as_object().unwrap().keys() {
|
for key in data.as_object().unwrap().keys() {
|
||||||
map.insert(key.to_owned(), parse(key, &data)?);
|
let property: DatabaseProperty = parse(key, &data)?;
|
||||||
|
|
||||||
|
map.insert(key.to_owned(), property.clone());
|
||||||
|
id_map.insert(property.id.clone(), property);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(DatabaseProperties { map })
|
Ok(DatabaseProperties { map, id_map })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,7 +769,7 @@ pub enum PropertyType {
|
||||||
Number,
|
Number,
|
||||||
Select(Select),
|
Select(Select),
|
||||||
MultiSelect(MultiSelect),
|
MultiSelect(MultiSelect),
|
||||||
Date(Date),
|
Date(Option<Date>),
|
||||||
Formula(Formula),
|
Formula(Formula),
|
||||||
Relation,
|
Relation,
|
||||||
Rollup,
|
Rollup,
|
||||||
|
@ -687,7 +784,6 @@ pub enum PropertyType {
|
||||||
CreatedBy,
|
CreatedBy,
|
||||||
LastEditedTime,
|
LastEditedTime,
|
||||||
LastEditedBy,
|
LastEditedBy,
|
||||||
Empty,
|
|
||||||
Unsupported(String, Value)
|
Unsupported(String, Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -713,7 +809,6 @@ pub enum DatabasePropertyType {
|
||||||
CreatedBy,
|
CreatedBy,
|
||||||
LastEditedTime,
|
LastEditedTime,
|
||||||
LastEditedBy,
|
LastEditedBy,
|
||||||
Empty,
|
|
||||||
Unsupported(String, Value)
|
Unsupported(String, Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -726,7 +821,7 @@ pub struct DatabaseFormula {
|
||||||
#[serde(try_from = "Value")]
|
#[serde(try_from = "Value")]
|
||||||
pub enum Formula {
|
pub enum Formula {
|
||||||
String(Option<String>),
|
String(Option<String>),
|
||||||
Number(Option<i32>),
|
Number(Option<f64>),
|
||||||
Boolean(Option<bool>),
|
Boolean(Option<bool>),
|
||||||
Date(Option<Date>),
|
Date(Option<Date>),
|
||||||
Unsupported(String, Value)
|
Unsupported(String, Value)
|
||||||
|
@ -750,7 +845,7 @@ impl TryFrom<Value> for Formula {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(try_from = "Value")]
|
#[serde(try_from = "Value")]
|
||||||
pub struct Select(pub SelectOption);
|
pub struct Select(pub Option<SelectOption>);
|
||||||
|
|
||||||
impl TryFrom<Value> for Select {
|
impl TryFrom<Value> for Select {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
@ -1040,7 +1135,11 @@ impl TryFrom<Value> for Property {
|
||||||
fn try_from(data: Value) -> Result<Property> {
|
fn try_from(data: Value) -> Result<Property> {
|
||||||
Ok(
|
Ok(
|
||||||
Property {
|
Property {
|
||||||
id: data.get("id").ok_or_else(|| Error::NoSuchProperty("id".to_string()))?.to_string(),
|
id: data.get("id")
|
||||||
|
.ok_or_else(|| Error::NoSuchProperty("id".to_string()))?
|
||||||
|
.as_str()
|
||||||
|
.unwrap() // FIXME: Remove unwrap
|
||||||
|
.to_string(),
|
||||||
next_url: match data.get("next_url") {
|
next_url: match data.get("next_url") {
|
||||||
Some(value) => Some(value.as_str().ok_or_else(|| Error::NoSuchProperty("next_url".to_string()))?.to_string()),
|
Some(value) => Some(value.as_str().ok_or_else(|| Error::NoSuchProperty("next_url".to_string()))?.to_string()),
|
||||||
None => None
|
None => None
|
||||||
|
@ -1066,6 +1165,7 @@ impl TryFrom<Value> for Property {
|
||||||
// FIXME: Convert to enum / PropertyType
|
// FIXME: Convert to enum / PropertyType
|
||||||
pub struct DatabaseProperty {
|
pub struct DatabaseProperty {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
pub next_url: Option<String>,
|
pub next_url: Option<String>,
|
||||||
#[serde(rename(serialize = "type"))]
|
#[serde(rename(serialize = "type"))]
|
||||||
pub kind: DatabasePropertyType
|
pub kind: DatabasePropertyType
|
||||||
|
@ -1075,17 +1175,24 @@ impl TryFrom<Value> for DatabaseProperty {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(data: Value) -> Result<DatabaseProperty> {
|
fn try_from(data: Value) -> Result<DatabaseProperty> {
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
DatabaseProperty {
|
DatabaseProperty {
|
||||||
id: data.get("id").ok_or_else(|| Error::NoSuchProperty("id".to_string()))?.to_string(),
|
id: data.get("id")
|
||||||
|
.ok_or_else(|| Error::NoSuchProperty("id".to_string()))?
|
||||||
|
.as_str()
|
||||||
|
.unwrap() // FIXME: Remove this unwrap
|
||||||
|
.to_string(),
|
||||||
|
|
||||||
next_url: match data.get("next_url") {
|
next_url: match data.get("next_url") {
|
||||||
Some(value) => Some(value.as_str().ok_or_else(|| Error::NoSuchProperty("next_url".to_string()))?.to_string()),
|
Some(value) => Some(value.as_str().ok_or_else(|| Error::NoSuchProperty("next_url".to_string()))?.to_string()),
|
||||||
None => None
|
None => None
|
||||||
},
|
},
|
||||||
|
name: parse::<String>("name", &data)?,
|
||||||
kind: match parse::<String>("type", &data)?.as_str() {
|
kind: match parse::<String>("type", &data)?.as_str() {
|
||||||
"title" => DatabasePropertyType::Title,
|
"title" => DatabasePropertyType::Title,
|
||||||
"rich_text" => DatabasePropertyType::RichText,
|
"rich_text" => DatabasePropertyType::RichText,
|
||||||
"date" => DatabasePropertyType::Date,
|
"date" | "created_time" | "last_edited_time" => DatabasePropertyType::Date,
|
||||||
"multi_select" => {
|
"multi_select" => {
|
||||||
// FIXME: Remove unwrap
|
// FIXME: Remove unwrap
|
||||||
let options = parse::<Vec<SelectOption>>("options", &data.get("multi_select").unwrap())?;
|
let options = parse::<Vec<SelectOption>>("options", &data.get("multi_select").unwrap())?;
|
||||||
|
@ -1098,6 +1205,16 @@ impl TryFrom<Value> for DatabaseProperty {
|
||||||
},
|
},
|
||||||
"formula" => DatabasePropertyType::Formula(parse("formula", &data)?),
|
"formula" => DatabasePropertyType::Formula(parse("formula", &data)?),
|
||||||
"checkbox" => DatabasePropertyType::Checkbox,
|
"checkbox" => DatabasePropertyType::Checkbox,
|
||||||
|
"number" => DatabasePropertyType::Number,
|
||||||
|
// TODO: "relation"
|
||||||
|
// TODO: "rollup"
|
||||||
|
// TODO: "people"
|
||||||
|
// TODO: "files"
|
||||||
|
// TODO: "url"
|
||||||
|
// TODO: "email"
|
||||||
|
// TODO: "phone_number"
|
||||||
|
// TODO: "created_by"
|
||||||
|
// TODO: "last_edited_by"
|
||||||
key => DatabasePropertyType::Unsupported(key.to_string(), data)
|
key => DatabasePropertyType::Unsupported(key.to_string(), data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1186,8 +1303,4 @@ impl TryFrom<Value> for Icon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
|
||||||
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
Ok(write!(formatter, "NotionError::{:?}", self)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue