Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ Cargo.lock

.api_token
todo_config.toml





# my own tests; do never merge it with remote
wowa
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ license = "MIT"

[dependencies]
serde_json = "1.0"
serde_path_to_error = "0.1.14"
thiserror = "1.0"
tracing = "0.1"

Expand All @@ -31,6 +32,15 @@ features = ["full"]
version = "1.0"
features = ["derive"]

[dependencies.uuid]
version = "1.2.2"
features = [
"v4", # Lets you generate random UUIDs
"fast-rng", # Use a faster (but still sufficiently random) RNG
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
"serde",
]

[dev-dependencies]
cargo-husky = "1"
wiremock = "0.5.2"
Expand Down
55 changes: 43 additions & 12 deletions src/ids.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::fmt::Display;
use std::fmt::Error;
use uuid::Uuid;

pub trait Identifier: Display {
fn value(&self) -> &str;
fn value(&self) -> String;
}
/// Meant to be a helpful trait allowing anything that can be
/// identified by the type specified in `ById`.
Expand All @@ -27,16 +28,15 @@ where
self
}
}

macro_rules! identifer {
macro_rules! uuid_identifer {
($name:ident) => {
#[derive(serde::Serialize, serde::Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
#[serde(transparent)]
pub struct $name(String);
pub struct $name(Uuid);

impl Identifier for $name {
fn value(&self) -> &str {
&self.0
fn value(&self) -> String {
self.0.to_string()
}
}

Expand All @@ -53,17 +53,48 @@ macro_rules! identifer {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok($name(s.to_string()))
Ok($name(Uuid::parse_str(s).unwrap()))
}
}
};
}

macro_rules! string_identifer {
($name:ident) => {
#[derive(serde::Serialize, serde::Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
#[serde(transparent)]
pub struct $name(String);

impl Identifier for $name {
fn value(&self) -> String {
self.0.clone()
}
}

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

impl std::str::FromStr for $name {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok($name(String::from_str(s).unwrap()))
}
}
};
}

identifer!(DatabaseId);
identifer!(PageId);
identifer!(BlockId);
identifer!(UserId);
identifer!(PropertyId);
// According to Notion API Reference id's are UUIDv4 (https://developers.notion.com/reference/intro#json-conventions)
// can be represented with or without dashes.
// Using uuid crate.
uuid_identifer!(DatabaseId);
uuid_identifer!(PageId);
uuid_identifer!(BlockId);
string_identifer!(UserId);
string_identifer!(PropertyId);

impl From<PageId> for BlockId {
fn from(page_id: PageId) -> Self {
Expand Down
51 changes: 47 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use crate::models::error::ErrorResponse;
use crate::models::search::{DatabaseQuery, SearchRequest};
use crate::models::{Block, Database, ListResponse, Object, Page};
use ids::{AsIdentifier, PageId};
use models::PageCreateRequest;
use models::paging::Paging;
use models::{PageCreateRequest, PageUpdateRequest};
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{header, Client, ClientBuilder, RequestBuilder};
use tracing::Instrument;
Expand All @@ -13,7 +14,7 @@ pub mod models;

pub use chrono;

const NOTION_API_VERSION: &str = "2022-02-22";
const NOTION_API_VERSION: &str = "2022-06-28";

/// An wrapper Error type for all errors produced by the [`NotionApi`](NotionApi) client.
#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -95,12 +96,17 @@ impl NotionApi {
.await
.map_err(|source| Error::ResponseIoError { source })?;

tracing::debug!("JSON Response: {}", json);
// tracing::trace!("JSON Response: {}", json);
#[cfg(test)]
{
dbg!(serde_json::from_str::<serde_json::Value>(&json)
.map_err(|source| Error::JsonParseError { source })?);
}
let deserializer = &mut serde_json::Deserializer::from_str(&json);
let result: Result<Object, _> = serde_path_to_error::deserialize(deserializer);
if let Err(e) = result {
panic!("{} . path: {}", e, e.path());
}
let result =
serde_json::from_str(&json).map_err(|source| Error::JsonParseError { source })?;

Expand Down Expand Up @@ -198,6 +204,27 @@ impl NotionApi {
}
}

pub async fn update_page<T: Into<PageUpdateRequest>>(
&self,
page_id: &PageId,
page_update: T,
) -> Result<Page, Error> {
let result = self
.make_json_request(
self.client
.patch(format!(
"https://api.notion.com/v1/pages/{}",
page_id.as_id()
))
.json(&page_update.into()),
)
.await?;
match result {
Object::Page { page } => Ok(page),
response => Err(Error::UnexpectedResponse { response }),
}
}

/// Query a database and return the matching pages.
pub async fn query_database<D, T>(
&self,
Expand Down Expand Up @@ -227,10 +254,26 @@ impl NotionApi {
pub async fn get_block_children<T: AsIdentifier<BlockId>>(
&self,
block_id: T,
paging: Paging,
) -> Result<ListResponse<Block>, Error> {
let query = {
[
paging.start_cursor.map(|s| format!("start_cursor={}", s)),
paging.page_size.map(|p| format!("page_size={}", p)),
]
.iter()
.filter_map(|i| i.clone())
.collect::<Vec<String>>()
.join("&")
};
let query = if query.is_empty() {
query
} else {
format!("?{query}")
};
let result = self
.make_json_request(self.client.get(&format!(
"https://api.notion.com/v1/blocks/{block_id}/children",
"https://api.notion.com/v1/blocks/{block_id}/children{query}",
block_id = block_id.as_id()
)))
.await?;
Expand Down
60 changes: 54 additions & 6 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ pub struct PageCreateRequest {
pub properties: Properties,
}

#[derive(Serialize, Debug, Eq, PartialEq)]
pub struct PageUpdateRequest {
pub properties: Properties,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct Page {
pub id: PageId,
Expand Down Expand Up @@ -395,12 +400,12 @@ pub struct TableOfContents {

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

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

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
Expand All @@ -411,7 +416,7 @@ pub struct LinkPreviewFields {
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct TemplateFields {
pub rich_text: Vec<RichText>,
pub children: Vec<Block>,
pub children: Option<Vec<Block>>,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
Expand All @@ -430,20 +435,20 @@ pub struct SyncedFromObject {
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct SyncedBlockFields {
pub synced_from: Option<SyncedFromObject>,
pub children: Vec<Block>,
pub children: Option<Vec<Block>>,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct TableFields {
pub table_width: u64,
pub has_column_header: bool,
pub has_row_header: bool,
pub children: Vec<Block>,
pub children: Option<Vec<Block>>,
}

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

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
Expand Down Expand Up @@ -615,6 +620,49 @@ pub enum Block {
Unknown,
}

impl Block {
pub fn has_children(&self) -> bool {
use Block::*;
match self {
Paragraph { common, .. }
| Heading1 { common, .. }
| Heading2 { common, .. }
| Heading3 { common, .. }
| Callout { common, .. }
| Quote { common, .. }
| BulletedListItem { common, .. }
| NumberedListItem { common, .. }
| ToDo { common, .. }
| Toggle { common, .. }
| Code { common, .. }
| ChildPage { common, .. }
| ChildDatabase { common, .. }
| Embed { common, .. }
| Image { common, .. }
| Video { common, .. }
| File { common, .. }
| Pdf { common, .. }
| Bookmark { common, .. }
| Equation { common, .. }
| Divider { common, .. }
| TableOfContents { common, .. }
| Breadcrumb { common, .. }
| ColumnList { common, .. }
| Column { common, .. }
| LinkPreview { common, .. }
| Template { common, .. }
| LinkToPage { common, .. }
| SyncedBlock { common, .. }
| Table { common, .. }
| TableRow { common, .. }
| Unsupported { common, .. } => common.has_children,
Unknown => {
panic!("Trying to reference identifier for unknown block!")
}
}
}
}

impl AsIdentifier<BlockId> for Block {
fn as_id(&self) -> &BlockId {
use Block::*;
Expand Down
9 changes: 9 additions & 0 deletions src/models/paging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ use serde::{Deserialize, Serialize};
#[serde(transparent)]
pub struct PagingCursor(String);

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

#[derive(Serialize, Debug, Eq, PartialEq, Default, Clone)]
pub struct Paging {
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
Loading