From 1ba9846bad1d5c927e0d7658a59097992ef56b60 Mon Sep 17 00:00:00 2001 From: Mark Cola Date: Sun, 26 Feb 2023 03:08:54 +1100 Subject: [PATCH] Add new CreateBlock children to page creation (#49) * extract block to mod * add CreateBlock struct, implement Into for Block, and add children to PageCreateRequest * move tests into block mod and fix up use --- src/lib.rs | 3 +- src/models/block.rs | 612 ++++++++++++++++++ src/models/block/tests.rs | 296 +++++++++ src/models/{ => block}/tests/callout.json | 0 .../{ => block}/tests/emoji_object.json | 0 .../tests/external_file_object.json | 0 src/models/{ => block}/tests/file_object.json | 0 src/models/{ => block}/tests/heading_1.json | 0 src/models/mod.rs | 463 +------------ src/models/properties.rs | 2 +- src/models/properties/tests.rs | 5 +- src/models/tests.rs | 288 +-------- ...h_text_mention_date_with_end_and_time.json | 2 +- 13 files changed, 924 insertions(+), 747 deletions(-) create mode 100644 src/models/block.rs create mode 100644 src/models/block/tests.rs rename src/models/{ => block}/tests/callout.json (100%) rename src/models/{ => block}/tests/emoji_object.json (100%) rename src/models/{ => block}/tests/external_file_object.json (100%) rename src/models/{ => block}/tests/file_object.json (100%) rename src/models/{ => block}/tests/heading_1.json (100%) diff --git a/src/lib.rs b/src/lib.rs index dca0158..8ad2c24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ use crate::ids::{BlockId, DatabaseId}; use crate::models::error::ErrorResponse; use crate::models::search::{DatabaseQuery, SearchRequest}; -use crate::models::{Block, Database, ListResponse, Object, Page}; +use crate::models::{Database, ListResponse, Object, Page}; use ids::{AsIdentifier, PageId}; +use models::block::Block; use models::PageCreateRequest; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{header, Client, ClientBuilder, RequestBuilder}; diff --git a/src/models/block.rs b/src/models/block.rs new file mode 100644 index 0000000..0d1b651 --- /dev/null +++ b/src/models/block.rs @@ -0,0 +1,612 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::ids::{AsIdentifier, BlockId, DatabaseId, PageId}; +use crate::models::text::{RichText, TextColor}; +use crate::models::users::UserCommon; + +mod tests; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct BlockCommon { + pub id: BlockId, + pub created_time: DateTime, + pub last_edited_time: DateTime, + pub has_children: bool, + pub created_by: UserCommon, + pub last_edited_by: UserCommon, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TextAndChildren { + pub rich_text: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option>, + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Text { + pub rich_text: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct InternalFileObject { + url: String, + expiry_time: DateTime, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ExternalFileObject { + url: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum FileOrEmojiObject { + Emoji { emoji: String }, + File { file: InternalFileObject }, + External { external: ExternalFileObject }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum FileObject { + File { file: InternalFileObject }, + External { external: ExternalFileObject }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Callout { + pub rich_text: Vec, + pub icon: FileOrEmojiObject, + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ToDoFields { + pub rich_text: Vec, + pub checked: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option>, + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ChildPageFields { + pub title: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ChildDatabaseFields { + pub title: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct EmbedFields { + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct BookmarkFields { + pub url: String, + pub caption: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum CodeLanguage { + Abap, + Arduino, + Bash, + Basic, + C, + Clojure, + Coffeescript, + #[serde(rename = "c++")] + CPlusPlus, + #[serde(rename = "c#")] + CSharp, + Css, + Dart, + Diff, + Docker, + Elixir, + Elm, + Erlang, + Flow, + Fortran, + #[serde(rename = "f#")] + FSharp, + Gherkin, + Glsl, + Go, + Graphql, + Groovy, + Haskell, + Html, + Java, + Javascript, + Json, + Julia, + Kotlin, + Latex, + Less, + Lisp, + Livescript, + Lua, + Makefile, + Markdown, + Markup, + Matlab, + Mermaid, + Nix, + #[serde(rename = "objective-c")] + ObjectiveC, + Ocaml, + Pascal, + Perl, + Php, + #[serde(rename = "plain text")] + PlainText, + Powershell, + Prolog, + Protobuf, + Python, + R, + Reason, + Ruby, + Rust, + Sass, + Scala, + Scheme, + Scss, + Shell, + Sql, + Swift, + Typescript, + #[serde(rename = "vb.net")] + VbNet, + Verilog, + Vhdl, + #[serde(rename = "visual basic")] + VisualBasic, + Webassembly, + Xml, + Yaml, + #[serde(rename = "java/c/c++/c#")] + JavaCAndCPlusPlusAndCSharp, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct CodeFields { + pub rich_text: Vec, + pub caption: Vec, + pub language: CodeLanguage, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Equation { + pub expression: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TableOfContents { + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ColumnListFields { + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ColumnFields { + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct LinkPreviewFields { + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TemplateFields { + pub rich_text: Vec, + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum LinkToPageFields { + PageId { page_id: PageId }, + DatabaseId { database_id: DatabaseId }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SyncedFromObject { + pub block_id: BlockId, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SyncedBlockFields { + pub synced_from: Option, + pub children: Vec, +} + +#[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, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TableRowFields { + pub cells: Vec, +} + +#[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, + }, + Callout { + #[serde(flatten)] + common: BlockCommon, + callout: Callout, + }, + Quote { + #[serde(flatten)] + common: BlockCommon, + quote: TextAndChildren, + }, + 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, + }, + Code { + #[serde(flatten)] + common: BlockCommon, + code: CodeFields, + }, + ChildPage { + #[serde(flatten)] + common: BlockCommon, + child_page: ChildPageFields, + }, + ChildDatabase { + #[serde(flatten)] + common: BlockCommon, + child_page: ChildDatabaseFields, + }, + Embed { + #[serde(flatten)] + common: BlockCommon, + embed: EmbedFields, + }, + Image { + #[serde(flatten)] + common: BlockCommon, + image: FileObject, + }, + Video { + #[serde(flatten)] + common: BlockCommon, + video: FileObject, + }, + File { + #[serde(flatten)] + common: BlockCommon, + file: FileObject, + caption: Text, + }, + Pdf { + #[serde(flatten)] + common: BlockCommon, + pdf: FileObject, + }, + Bookmark { + #[serde(flatten)] + common: BlockCommon, + bookmark: BookmarkFields, + }, + Equation { + #[serde(flatten)] + common: BlockCommon, + equation: Equation, + }, + Divider { + #[serde(flatten)] + common: BlockCommon, + }, + TableOfContents { + #[serde(flatten)] + common: BlockCommon, + table_of_contents: TableOfContents, + }, + Breadcrumb { + #[serde(flatten)] + common: BlockCommon, + }, + ColumnList { + #[serde(flatten)] + common: BlockCommon, + column_list: ColumnListFields, + }, + Column { + #[serde(flatten)] + common: BlockCommon, + column: ColumnFields, + }, + LinkPreview { + #[serde(flatten)] + common: BlockCommon, + link_preview: LinkPreviewFields, + }, + Template { + #[serde(flatten)] + common: BlockCommon, + template: TemplateFields, + }, + LinkToPage { + #[serde(flatten)] + common: BlockCommon, + link_to_page: LinkToPageFields, + }, + Table { + #[serde(flatten)] + common: BlockCommon, + table: TableFields, + }, + SyncedBlock { + #[serde(flatten)] + common: BlockCommon, + synced_block: SyncedBlockFields, + }, + TableRow { + #[serde(flatten)] + common: BlockCommon, + table_row: TableRowFields, + }, + Unsupported { + #[serde(flatten)] + common: BlockCommon, + }, + #[serde(other)] + Unknown, +} + +impl AsIdentifier for Block { + fn as_id(&self) -> &BlockId { + 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.id, + Unknown => { + panic!("Trying to reference identifier for unknown block!") + } + } + } +} + +impl Into for Block { + fn into(self) -> CreateBlock { + match self { + Block::Paragraph { paragraph, .. } => CreateBlock::Paragraph { paragraph }, + Block::Heading1 { heading_1, .. } => CreateBlock::Heading1 { heading_1 }, + Block::Heading2 { heading_2, .. } => CreateBlock::Heading2 { heading_2 }, + Block::Heading3 { heading_3, .. } => CreateBlock::Heading3 { heading_3 }, + Block::Callout { callout, .. } => CreateBlock::Callout { callout }, + Block::Quote { quote, .. } => CreateBlock::Quote { quote }, + Block::BulletedListItem { + bulleted_list_item, .. + } => CreateBlock::BulletedListItem { bulleted_list_item }, + Block::NumberedListItem { + numbered_list_item, .. + } => CreateBlock::NumberedListItem { numbered_list_item }, + Block::ToDo { to_do, .. } => CreateBlock::ToDo { to_do }, + Block::Toggle { toggle, .. } => CreateBlock::Toggle { toggle }, + Block::Code { code, .. } => CreateBlock::Code { code }, + Block::ChildPage { child_page, .. } => CreateBlock::ChildPage { child_page }, + Block::ChildDatabase { child_page, .. } => CreateBlock::ChildDatabase { child_page }, + Block::Embed { embed, .. } => CreateBlock::Embed { embed }, + Block::Image { image, .. } => CreateBlock::Image { image }, + Block::Video { video, .. } => CreateBlock::Video { video }, + Block::File { file, caption, .. } => CreateBlock::File { file, caption }, + Block::Pdf { pdf, .. } => CreateBlock::Pdf { pdf }, + Block::Bookmark { bookmark, .. } => CreateBlock::Bookmark { bookmark }, + Block::Equation { equation, .. } => CreateBlock::Equation { equation }, + Block::Divider { .. } => CreateBlock::Divider {}, + Block::TableOfContents { + table_of_contents, .. + } => CreateBlock::TableOfContents { table_of_contents }, + Block::Breadcrumb { .. } => CreateBlock::Breadcrumb {}, + Block::ColumnList { column_list, .. } => CreateBlock::ColumnList { column_list }, + Block::Column { column, .. } => CreateBlock::Column { column }, + + Block::LinkPreview { link_preview, .. } => CreateBlock::LinkPreview { link_preview }, + Block::Template { template, .. } => CreateBlock::Template { template }, + Block::LinkToPage { link_to_page, .. } => CreateBlock::LinkToPage { link_to_page }, + Block::Table { table, .. } => CreateBlock::Table { table }, + Block::SyncedBlock { synced_block, .. } => CreateBlock::SyncedBlock { synced_block }, + Block::TableRow { table_row, .. } => CreateBlock::TableRow { table_row }, + Block::Unsupported { .. } => CreateBlock::Unsupported, + Block::Unknown => CreateBlock::Unknown, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum CreateBlock { + Paragraph { + paragraph: TextAndChildren, + }, + #[serde(rename = "heading_1")] + Heading1 { + heading_1: Text, + }, + #[serde(rename = "heading_2")] + Heading2 { + heading_2: Text, + }, + #[serde(rename = "heading_3")] + Heading3 { + heading_3: Text, + }, + Callout { + callout: Callout, + }, + Quote { + quote: TextAndChildren, + }, + BulletedListItem { + bulleted_list_item: TextAndChildren, + }, + NumberedListItem { + numbered_list_item: TextAndChildren, + }, + ToDo { + to_do: ToDoFields, + }, + Toggle { + toggle: TextAndChildren, + }, + Code { + code: CodeFields, + }, + ChildPage { + child_page: ChildPageFields, + }, + ChildDatabase { + child_page: ChildDatabaseFields, + }, + Embed { + embed: EmbedFields, + }, + Image { + image: FileObject, + }, + Video { + video: FileObject, + }, + File { + file: FileObject, + caption: Text, + }, + Pdf { + pdf: FileObject, + }, + Bookmark { + bookmark: BookmarkFields, + }, + Equation { + equation: Equation, + }, + Divider, + TableOfContents { + table_of_contents: TableOfContents, + }, + Breadcrumb, + ColumnList { + column_list: ColumnListFields, + }, + Column { + column: ColumnFields, + }, + LinkPreview { + link_preview: LinkPreviewFields, + }, + Template { + template: TemplateFields, + }, + LinkToPage { + link_to_page: LinkToPageFields, + }, + Table { + table: TableFields, + }, + SyncedBlock { + synced_block: SyncedBlockFields, + }, + TableRow { + table_row: TableRowFields, + }, + Unsupported, + #[serde(other)] + Unknown, +} diff --git a/src/models/block/tests.rs b/src/models/block/tests.rs new file mode 100644 index 0000000..18306a6 --- /dev/null +++ b/src/models/block/tests.rs @@ -0,0 +1,296 @@ +#[cfg(test)] +mod tests { + use crate::ids::{BlockId, UserId}; + use crate::models::block::{ + Block, BlockCommon, Callout, ExternalFileObject, FileOrEmojiObject, InternalFileObject, + Text as TextBlockModel, + }; + use crate::models::text::{Annotations, RichText, RichTextCommon, Text, TextColor}; + use crate::models::users::UserCommon; + use crate::models::Object; + use chrono::DateTime; + use std::str::FromStr; + + #[test] + fn heading_1() { + let heading_1: Block = serde_json::from_str(include_str!("tests/heading_1.json")).unwrap(); + assert_eq!( + heading_1, + Block::Heading1 { + common: BlockCommon { + id: BlockId::from_str("9e891834-6a03-475c-a2b8-421e17f0f3aa").unwrap(), + created_time: DateTime::from_str("2022-05-12T21:15:00.000Z").unwrap(), + last_edited_time: DateTime::from_str("2022-05-12T22:10:00.000Z").unwrap(), + has_children: false, + created_by: UserCommon { + id: UserId::from_str("6419f912-5293-4ea8-b2c8-9c3ce44f90e3").unwrap(), + name: None, + avatar_url: None, + }, + last_edited_by: UserCommon { + id: UserId::from_str("6419f912-5293-4ea8-b2c8-9c3ce44f90e3").unwrap(), + name: None, + avatar_url: None, + }, + }, + heading_1: TextBlockModel { + rich_text: vec![ + RichText::Text { + rich_text: RichTextCommon { + plain_text: "This".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(true), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: "This".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "is".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(true), + }), + }, + text: Text { + content: "is".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "a".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(true), + strikethrough: Some(false), + underline: Some(true), + }), + }, + text: Text { + content: "a".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "Heading".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(true), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: "Heading".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "1".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(true), + underline: Some(false), + }), + }, + text: Text { + content: "1".to_string(), + link: None, + }, + }, + ] + }, + } + ) + } + + #[test] + fn emoji_object() { + let emoji_object: FileOrEmojiObject = + serde_json::from_str(include_str!("tests/emoji_object.json")).unwrap(); + assert_eq!( + emoji_object, + FileOrEmojiObject::Emoji { + emoji: "💡".to_string() + } + ) + } + + #[test] + fn file_object() { + let file_object: FileOrEmojiObject = + serde_json::from_str(include_str!("tests/file_object.json")).unwrap(); + assert_eq!(file_object, FileOrEmojiObject::File { + file: InternalFileObject { + url: "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/2703e742-ace5-428c-a74d-1c587ceddc32/DiRT_Rally.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220513%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220513T201035Z&X-Amz-Expires=3600&X-Amz-Signature=714b49bde0b499fb8f3aae1a88a8cbd374f2b09c1d128e91cac49e85ce0e00fb&X-Amz-SignedHeaders=host&x-id=GetObject".to_string(), + expiry_time: DateTime::from_str("2022-05-13T21:10:35.817Z").unwrap(), + } + }) + } + + #[test] + fn external_file_object() { + let external_file_object: FileOrEmojiObject = + serde_json::from_str(include_str!("tests/external_file_object.json")).unwrap(); + assert_eq!( + external_file_object, + FileOrEmojiObject::External { + external: ExternalFileObject { + url: "https://nerdist.com/wp-content/uploads/2020/07/maxresdefault.jpg" + .to_string(), + } + } + ) + } + + #[test] + fn callout() { + let callout: Object = serde_json::from_str(include_str!("tests/callout.json")).unwrap(); + assert_eq!( + callout, + Object::Block { + block: Block::Callout { + common: BlockCommon { + id: BlockId::from_str("00e8829a-a7b8-4075-884a-8f53be145d2f").unwrap(), + created_time: DateTime::from_str("2022-05-13T20:08:00.000Z").unwrap(), + last_edited_time: DateTime::from_str("2022-05-13T20:08:00.000Z").unwrap(), + has_children: true, + created_by: UserCommon { + id: UserId::from_str("e2507360-468c-4e0f-a928-7bbcbbb45353").unwrap(), + name: None, + avatar_url: None, + }, + last_edited_by: UserCommon { + id: UserId::from_str("e2507360-468c-4e0f-a928-7bbcbbb45353").unwrap(), + name: None, + avatar_url: None, + }, + }, + callout: Callout { + rich_text: vec![RichText::Text { + rich_text: RichTextCommon { + plain_text: "Test callout".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: "Test callout".to_string(), + link: None + }, + }], + icon: FileOrEmojiObject::Emoji { + emoji: "💡".to_string() + }, + color: TextColor::Green, + }, + } + } + ) + } +} diff --git a/src/models/tests/callout.json b/src/models/block/tests/callout.json similarity index 100% rename from src/models/tests/callout.json rename to src/models/block/tests/callout.json diff --git a/src/models/tests/emoji_object.json b/src/models/block/tests/emoji_object.json similarity index 100% rename from src/models/tests/emoji_object.json rename to src/models/block/tests/emoji_object.json diff --git a/src/models/tests/external_file_object.json b/src/models/block/tests/external_file_object.json similarity index 100% rename from src/models/tests/external_file_object.json rename to src/models/block/tests/external_file_object.json diff --git a/src/models/tests/file_object.json b/src/models/block/tests/file_object.json similarity index 100% rename from src/models/tests/file_object.json rename to src/models/block/tests/file_object.json diff --git a/src/models/tests/heading_1.json b/src/models/block/tests/heading_1.json similarity index 100% rename from src/models/tests/heading_1.json rename to src/models/block/tests/heading_1.json diff --git a/src/models/mod.rs b/src/models/mod.rs index 3c9d12b..795accd 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,3 +1,4 @@ +pub mod block; pub mod error; pub mod paging; pub mod properties; @@ -8,15 +9,16 @@ pub mod text; pub mod users; use crate::models::properties::{PropertyConfiguration, PropertyValue}; -use crate::models::text::{RichText, TextColor}; +use crate::models::text::RichText; use crate::Error; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::ids::{AsIdentifier, BlockId, DatabaseId, PageId}; +use crate::ids::{AsIdentifier, DatabaseId, PageId}; +use crate::models::block::{Block, CreateBlock}; use crate::models::error::ErrorResponse; use crate::models::paging::PagingCursor; -use crate::models::users::{User, UserCommon}; +use crate::models::users::User; pub use chrono::{DateTime, Utc}; pub use serde_json::value::Number; @@ -184,6 +186,8 @@ impl Properties { pub struct PageCreateRequest { pub parent: Parent, pub properties: Properties, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option>, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] @@ -205,459 +209,6 @@ impl Page { } } -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct BlockCommon { - pub id: BlockId, - pub created_time: DateTime, - pub last_edited_time: DateTime, - pub has_children: bool, - pub created_by: UserCommon, - pub last_edited_by: UserCommon, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct TextAndChildren { - pub rich_text: Vec, - pub children: Option>, - pub color: TextColor, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct Text { - pub rich_text: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct InternalFileObject { - url: String, - expiry_time: DateTime, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct ExternalFileObject { - url: String, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -pub enum FileOrEmojiObject { - Emoji { emoji: String }, - File { file: InternalFileObject }, - External { external: ExternalFileObject }, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -pub enum FileObject { - File { file: InternalFileObject }, - External { external: ExternalFileObject }, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct Callout { - pub rich_text: Vec, - pub icon: FileOrEmojiObject, - pub color: TextColor, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct ToDoFields { - pub rich_text: Vec, - pub checked: bool, - pub children: Option>, - pub color: TextColor, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct ChildPageFields { - pub title: String, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct ChildDatabaseFields { - pub title: String, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct EmbedFields { - pub url: String, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct BookmarkFields { - pub url: String, - pub caption: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -#[serde(rename_all = "lowercase")] -pub enum CodeLanguage { - Abap, - Arduino, - Bash, - Basic, - C, - Clojure, - Coffeescript, - #[serde(rename = "c++")] - CPlusPlus, - #[serde(rename = "c#")] - CSharp, - Css, - Dart, - Diff, - Docker, - Elixir, - Elm, - Erlang, - Flow, - Fortran, - #[serde(rename = "f#")] - FSharp, - Gherkin, - Glsl, - Go, - Graphql, - Groovy, - Haskell, - Html, - Java, - Javascript, - Json, - Julia, - Kotlin, - Latex, - Less, - Lisp, - Livescript, - Lua, - Makefile, - Markdown, - Markup, - Matlab, - Mermaid, - Nix, - #[serde(rename = "objective-c")] - ObjectiveC, - Ocaml, - Pascal, - Perl, - Php, - #[serde(rename = "plain text")] - PlainText, - Powershell, - Prolog, - Protobuf, - Python, - R, - Reason, - Ruby, - Rust, - Sass, - Scala, - Scheme, - Scss, - Shell, - Sql, - Swift, - Typescript, - #[serde(rename = "vb.net")] - VbNet, - Verilog, - Vhdl, - #[serde(rename = "visual basic")] - VisualBasic, - Webassembly, - Xml, - Yaml, - #[serde(rename = "java/c/c++/c#")] - JavaCAndCPlusPlusAndCSharp, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct CodeFields { - pub rich_text: Vec, - pub caption: Vec, - pub language: CodeLanguage, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct Equation { - pub expression: String, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct TableOfContents { - pub color: TextColor, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct ColumnListFields { - pub children: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct ColumnFields { - pub children: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct LinkPreviewFields { - pub url: String, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct TemplateFields { - pub rich_text: Vec, - pub children: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -pub enum LinkToPageFields { - PageId { page_id: PageId }, - DatabaseId { database_id: DatabaseId }, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct SyncedFromObject { - pub block_id: BlockId, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct SyncedBlockFields { - pub synced_from: Option, - pub children: Vec, -} - -#[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, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct TableRowFields { - pub cells: Vec, -} - -#[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, - }, - Callout { - #[serde(flatten)] - common: BlockCommon, - callout: Callout, - }, - Quote { - #[serde(flatten)] - common: BlockCommon, - quote: TextAndChildren, - }, - 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, - }, - Code { - #[serde(flatten)] - common: BlockCommon, - code: CodeFields, - }, - ChildPage { - #[serde(flatten)] - common: BlockCommon, - child_page: ChildPageFields, - }, - ChildDatabase { - #[serde(flatten)] - common: BlockCommon, - child_page: ChildDatabaseFields, - }, - Embed { - #[serde(flatten)] - common: BlockCommon, - embed: EmbedFields, - }, - Image { - #[serde(flatten)] - common: BlockCommon, - image: FileObject, - }, - Video { - #[serde(flatten)] - common: BlockCommon, - video: FileObject, - }, - File { - #[serde(flatten)] - common: BlockCommon, - file: FileObject, - caption: Text, - }, - Pdf { - #[serde(flatten)] - common: BlockCommon, - pdf: FileObject, - }, - Bookmark { - #[serde(flatten)] - common: BlockCommon, - bookmark: BookmarkFields, - }, - Equation { - #[serde(flatten)] - common: BlockCommon, - equation: Equation, - }, - Divider { - #[serde(flatten)] - common: BlockCommon, - }, - TableOfContents { - #[serde(flatten)] - common: BlockCommon, - table_of_contents: TableOfContents, - }, - Breadcrumb { - #[serde(flatten)] - common: BlockCommon, - }, - ColumnList { - #[serde(flatten)] - common: BlockCommon, - column_list: ColumnListFields, - }, - Column { - #[serde(flatten)] - common: BlockCommon, - column: ColumnFields, - }, - LinkPreview { - #[serde(flatten)] - common: BlockCommon, - link_preview: LinkPreviewFields, - }, - Template { - #[serde(flatten)] - common: BlockCommon, - template: TemplateFields, - }, - LinkToPage { - #[serde(flatten)] - common: BlockCommon, - link_to_page: LinkToPageFields, - }, - Table { - #[serde(flatten)] - common: BlockCommon, - table: TableFields, - }, - SyncedBlock { - #[serde(flatten)] - common: BlockCommon, - synced_block: SyncedBlockFields, - }, - TableRow { - #[serde(flatten)] - common: BlockCommon, - table_row: TableRowFields, - }, - Unsupported { - #[serde(flatten)] - common: BlockCommon, - }, - #[serde(other)] - Unknown, -} - -impl AsIdentifier for Block { - fn as_id(&self) -> &BlockId { - 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.id, - Unknown => { - panic!("Trying to reference identifier for unknown block!") - } - } - } -} - impl AsIdentifier for Page { fn as_id(&self) -> &PageId { &self.id diff --git a/src/models/properties.rs b/src/models/properties.rs index fcead03..918eed6 100644 --- a/src/models/properties.rs +++ b/src/models/properties.rs @@ -1,8 +1,8 @@ use crate::models::text::RichText; use crate::models::users::User; -use super::{DateTime, Number, Utc}; use crate::ids::{DatabaseId, PageId, PropertyId}; +use crate::models::{DateTime, Number, Utc}; use chrono::NaiveDate; use serde::{Deserialize, Serialize}; diff --git a/src/models/properties/tests.rs b/src/models/properties/tests.rs index 86bead8..8073c94 100644 --- a/src/models/properties/tests.rs +++ b/src/models/properties/tests.rs @@ -1,4 +1,7 @@ -use super::{DateOrDateTime, PropertyValue, RollupPropertyValue, RollupValue}; +use crate::models::{ + properties::{DateOrDateTime, RollupPropertyValue, RollupValue}, + PropertyValue, +}; use chrono::NaiveDate; #[test] diff --git a/src/models/tests.rs b/src/models/tests.rs index 1f41991..cf13153 100644 --- a/src/models/tests.rs +++ b/src/models/tests.rs @@ -4,11 +4,7 @@ use crate::models::text::{ Annotations, Link, MentionObject, RichText, RichTextCommon, Text, TextColor, }; use crate::models::users::{Person, User, UserCommon}; -use crate::models::{ - BlockCommon, Callout, ExternalFileObject, FileOrEmojiObject, InternalFileObject, ListResponse, - Object, Page, -}; -use crate::{models, Block, BlockId}; +use crate::models::{ListResponse, Object, Page}; use chrono::{DateTime, NaiveDate}; use std::str::FromStr; @@ -226,285 +222,3 @@ fn rich_text_mention_date_with_end_and_time() { } ) } - -#[test] -fn heading_1() { - let heading_1: Block = serde_json::from_str(include_str!("tests/heading_1.json")).unwrap(); - assert_eq!( - heading_1, - Block::Heading1 { - common: BlockCommon { - id: BlockId::from_str("9e891834-6a03-475c-a2b8-421e17f0f3aa").unwrap(), - created_time: DateTime::from_str("2022-05-12T21:15:00.000Z").unwrap(), - last_edited_time: DateTime::from_str("2022-05-12T22:10:00.000Z").unwrap(), - has_children: false, - created_by: UserCommon { - id: UserId::from_str("6419f912-5293-4ea8-b2c8-9c3ce44f90e3").unwrap(), - name: None, - avatar_url: None, - }, - last_edited_by: UserCommon { - id: UserId::from_str("6419f912-5293-4ea8-b2c8-9c3ce44f90e3").unwrap(), - name: None, - avatar_url: None, - }, - }, - heading_1: models::Text { - rich_text: vec![ - RichText::Text { - rich_text: RichTextCommon { - plain_text: "This".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(true), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(false), - underline: Some(false), - }), - }, - text: Text { - content: "This".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: " ".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(false), - underline: Some(false), - }), - }, - text: Text { - content: " ".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: "is".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(false), - underline: Some(true), - }), - }, - text: Text { - content: "is".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: " ".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(false), - underline: Some(false), - }), - }, - text: Text { - content: " ".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: "a".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(true), - strikethrough: Some(false), - underline: Some(true), - }), - }, - text: Text { - content: "a".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: " ".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(false), - underline: Some(false), - }), - }, - text: Text { - content: " ".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: "Heading".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(true), - strikethrough: Some(false), - underline: Some(false), - }), - }, - text: Text { - content: "Heading".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: " ".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(false), - underline: Some(false), - }), - }, - text: Text { - content: " ".to_string(), - link: None, - }, - }, - RichText::Text { - rich_text: RichTextCommon { - plain_text: "1".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(true), - underline: Some(false), - }), - }, - text: Text { - content: "1".to_string(), - link: None, - }, - }, - ] - }, - } - ) -} - -#[test] -fn emoji_object() { - let emoji_object: FileOrEmojiObject = - serde_json::from_str(include_str!("tests/emoji_object.json")).unwrap(); - assert_eq!( - emoji_object, - FileOrEmojiObject::Emoji { - emoji: "💡".to_string() - } - ) -} - -#[test] -fn file_object() { - let file_object: FileOrEmojiObject = - serde_json::from_str(include_str!("tests/file_object.json")).unwrap(); - assert_eq!(file_object, FileOrEmojiObject::File { - file: InternalFileObject { - url: "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/2703e742-ace5-428c-a74d-1c587ceddc32/DiRT_Rally.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220513%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220513T201035Z&X-Amz-Expires=3600&X-Amz-Signature=714b49bde0b499fb8f3aae1a88a8cbd374f2b09c1d128e91cac49e85ce0e00fb&X-Amz-SignedHeaders=host&x-id=GetObject".to_string(), - expiry_time: DateTime::from_str("2022-05-13T21:10:35.817Z").unwrap(), - } - }) -} - -#[test] -fn external_file_object() { - let external_file_object: FileOrEmojiObject = - serde_json::from_str(include_str!("tests/external_file_object.json")).unwrap(); - assert_eq!( - external_file_object, - FileOrEmojiObject::External { - external: ExternalFileObject { - url: "https://nerdist.com/wp-content/uploads/2020/07/maxresdefault.jpg".to_string(), - } - } - ) -} - -#[test] -fn callout() { - let callout: Object = serde_json::from_str(include_str!("tests/callout.json")).unwrap(); - assert_eq!( - callout, - Object::Block { - block: Block::Callout { - common: BlockCommon { - id: BlockId::from_str("00e8829a-a7b8-4075-884a-8f53be145d2f").unwrap(), - created_time: DateTime::from_str("2022-05-13T20:08:00.000Z").unwrap(), - last_edited_time: DateTime::from_str("2022-05-13T20:08:00.000Z").unwrap(), - has_children: true, - created_by: UserCommon { - id: UserId::from_str("e2507360-468c-4e0f-a928-7bbcbbb45353").unwrap(), - name: None, - avatar_url: None, - }, - last_edited_by: UserCommon { - id: UserId::from_str("e2507360-468c-4e0f-a928-7bbcbbb45353").unwrap(), - name: None, - avatar_url: None, - }, - }, - callout: Callout { - rich_text: vec![RichText::Text { - rich_text: RichTextCommon { - plain_text: "Test callout".to_string(), - href: None, - annotations: Some(Annotations { - bold: Some(false), - code: Some(false), - color: Some(TextColor::Default), - italic: Some(false), - strikethrough: Some(false), - underline: Some(false), - }), - }, - text: Text { - content: "Test callout".to_string(), - link: None - }, - }], - icon: FileOrEmojiObject::Emoji { - emoji: "💡".to_string() - }, - color: TextColor::Green, - }, - } - } - ) -} diff --git a/src/models/tests/rich_text_mention_date_with_end_and_time.json b/src/models/tests/rich_text_mention_date_with_end_and_time.json index 2070207..1f22c3c 100644 --- a/src/models/tests/rich_text_mention_date_with_end_and_time.json +++ b/src/models/tests/rich_text_mention_date_with_end_and_time.json @@ -18,4 +18,4 @@ }, "plain_text": "2022-04-16T12:00:00.000-04:00 → 2022-04-16T12:00:00.000-04:00", "href": null -} \ No newline at end of file +}