Skip to content

Commit

Permalink
Get Children Blocks (#9)
Browse files Browse the repository at this point in the history
* wip

* generic block

* refactor

* all blocks

* format

* PR Feedback

* more feedback

Co-authored-by: Brett Spradling <bspradling@godaddy.com>
  • Loading branch information
bspradling and bspradling-godaddy committed May 17, 2021
1 parent e498b3c commit 68819a8
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 7 deletions.
45 changes: 41 additions & 4 deletions src/lib.rs
@@ -1,11 +1,11 @@
use crate::models::search::{DatabaseQuery, SearchRequest};
use crate::models::{Database, DatabaseId, ListResponse, Object, Page};
use crate::models::{Block, BlockId, Database, DatabaseId, ListResponse, Object, Page};
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{header, Client, ClientBuilder, RequestBuilder};
use serde::de::DeserializeOwned;
use snafu::{ResultExt, Snafu};

mod models;
pub mod models;

const NOTION_API_VERSION: &str = "2021-05-13";

Expand Down Expand Up @@ -127,6 +127,17 @@ impl NotionApi {
)
.await?)
}

pub async fn get_block_children<T: Identifiable<Type = BlockId>>(
&self,
block_id: T,
) -> Result<ListResponse<Block>, Error> {
Ok(NotionApi::make_json_request(self.client.get(&format!(
"https://api.notion.com/v1/blocks/{block_id}/children",
block_id = block_id.id()
)))
.await?)
}
}

#[cfg(test)]
Expand All @@ -135,8 +146,8 @@ mod tests {
use crate::models::search::{
DatabaseQuery, FilterCondition, FilterProperty, FilterValue, NotionSearch, TextCondition,
};
use crate::models::Object;
use crate::NotionApi;
use crate::models::{BlockId, Object};
use crate::{Identifiable, NotionApi};

fn test_token() -> String {
let token = {
Expand Down Expand Up @@ -226,6 +237,32 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn get_block_children() -> Result<(), Box<dyn std::error::Error>> {
let api = test_client();

let search_response = api
.search(NotionSearch::Filter {
value: FilterValue::Page,
property: FilterProperty::Object,
})
.await?;

println!("{:?}", search_response.results.len());

for object in search_response.results {
match object {
Object::Page { page } => api
.get_block_children(BlockId::from(page.id()))
.await
.unwrap(),
_ => panic!("Should not have received anything but pages!"),
};
}

Ok(())
}

#[tokio::test]
async fn query_database() -> Result<(), Box<dyn std::error::Error>> {
let api = test_client();
Expand Down
151 changes: 148 additions & 3 deletions src/models.rs
Expand Up @@ -136,12 +136,130 @@ pub struct Page {
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct Block {}
pub struct BlockCommon {
id: BlockId,
created_time: DateTime<Utc>,
last_edited_time: DateTime<Utc>,
has_children: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct TextAndChildren {
text: Vec<RichText>,
children: Option<Vec<Block>>,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct Text {
text: Vec<RichText>,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct ToDoFields {
text: Vec<RichText>,
checked: bool,
children: Option<Vec<Block>>,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct ChildPageFields {
title: String,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum Block {
Paragraph {
#[serde(flatten)]
common: BlockCommon,
paragraph: TextAndChildren,
},
#[serde(rename = "heading_1")]
Heading1 {
#[serde(flatten)]
common: BlockCommon,
heading_1: Text,
},
#[serde(rename = "heading_2")]
Heading2 {
#[serde(flatten)]
common: BlockCommon,
heading_2: Text,
},
#[serde(rename = "heading_3")]
Heading3 {
#[serde(flatten)]
common: BlockCommon,
heading_3: Text,
},
BulletedListItem {
#[serde(flatten)]
common: BlockCommon,
bulleted_list_item: TextAndChildren,
},
NumberedListItem {
#[serde(flatten)]
common: BlockCommon,
numbered_list_item: TextAndChildren,
},
ToDo {
#[serde(flatten)]
common: BlockCommon,
to_do: ToDoFields,
},
Toggle {
#[serde(flatten)]
common: BlockCommon,
toggle: TextAndChildren,
},
ChildPage {
#[serde(flatten)]
common: BlockCommon,
child_page: ChildPageFields,
},
#[serde(other)]
Unsupported,
}

impl Identifiable for Block {
type Type = BlockId;

fn id(&self) -> &Self::Type {
use Block::*;
match self {
Paragraph { common, .. }
| Heading1 { common, .. }
| Heading2 { common, .. }
| Heading3 { common, .. }
| BulletedListItem { common, .. }
| NumberedListItem { common, .. }
| ToDo { common, .. }
| Toggle { common, .. }
| ChildPage { common, .. } => &common.id,
Unsupported {} => {
panic!("Trying to reference identifier for unsupported block!")
}
}
}
}

impl Identifiable for Page {
type Type = PageId;

fn id(&self) -> &Self::Type {
&self.id
}
}

#[derive(Eq, Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(tag = "object")]
#[serde(rename_all = "snake_case")]
pub enum Object {
Block {
#[serde(flatten)]
block: Block,
},
Database {
#[serde(flatten)]
database: Database,
Expand All @@ -158,7 +276,34 @@ pub enum Object {
#[serde(flatten)]
user: User,
},
Block {},
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
#[serde(transparent)]
pub struct BlockId(String);

impl BlockId {
pub fn id(&self) -> &str {
&self.0
}

pub fn from(page_id: &PageId) -> Self {
BlockId(page_id.clone().0)
}
}

impl Identifiable for BlockId {
type Type = BlockId;

fn id(&self) -> &Self::Type {
self
}
}

impl Display for BlockId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl Object {
Expand Down

0 comments on commit 68819a8

Please sign in to comment.