Skip to main content

notion_client/objects/
block.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4
5use super::{
6    emoji::Emoji, file::File, native_icon::NativeIcon, parent::Parent, rich_text::RichText,
7    user::User,
8};
9
10#[skip_serializing_none]
11#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
12pub struct Block {
13    pub object: Option<String>,
14    pub id: Option<String>,
15    pub parent: Option<Parent>,
16    #[serde(flatten)]
17    pub block_type: BlockType,
18    pub created_time: Option<DateTime<Utc>>,
19    pub created_by: Option<User>,
20    pub last_edited_time: Option<DateTime<Utc>>,
21    pub last_edited_by: Option<User>,
22    pub in_trash: Option<bool>,
23    pub has_children: Option<bool>,
24}
25
26#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
27#[serde(tag = "type", rename_all = "snake_case")]
28pub enum BlockType {
29    #[default]
30    None,
31    Bookmark {
32        bookmark: BookmarkValue,
33    },
34    Breadcrumb {
35        breadcrump: BreadcrumpValue,
36    },
37    BulletedListItem {
38        bulleted_list_item: BulletedListItemValue,
39    },
40    Callout {
41        callout: CalloutValue,
42    },
43    ChildDatabase {
44        child_database: ChildDatabaseValue,
45    },
46    ChildPage {
47        child_page: ChildPageValue,
48    },
49    Code {
50        code: CodeValue,
51    },
52    ColumnList {
53        column_list: ColumnListValue,
54    },
55    Column {
56        column: ColumnValue,
57    },
58    Divider {
59        divider: DividerValue,
60    },
61    Embed {
62        embed: EmbedValue,
63    },
64    Equation {
65        equation: EquationValue,
66    },
67    File {
68        file: FileValue,
69    },
70    #[serde(rename = "heading_1")]
71    Heading1 {
72        heading_1: HeadingsValue,
73    },
74    #[serde(rename = "heading_2")]
75    Heading2 {
76        heading_2: HeadingsValue,
77    },
78    #[serde(rename = "heading_3")]
79    Heading3 {
80        heading_3: HeadingsValue,
81    },
82    Image {
83        image: ImageValue,
84    },
85    LinkPreview {
86        link_preview: LinkPreviewValue,
87    },
88    NumberedListItem {
89        numbered_list_item: NumberedListItemValue,
90    },
91    Paragraph {
92        paragraph: ParagraphValue,
93    },
94    Pdf {
95        pdf: PdfValue,
96    },
97    Quote {
98        quote: QuoteValue,
99    },
100    SyncedBlock {
101        synced_block: SyncedBlockValue,
102    },
103    Table {
104        table: TableValue,
105    },
106    TableOfContents {
107        table_of_contents: TableOfContentsValue,
108    },
109    TableRow {
110        table_row: TableRowsValue,
111    },
112    Template {
113        template: TemplateValue,
114    },
115    ToDo {
116        to_do: ToDoValue,
117    },
118    Toggle {
119        toggle: ToggleValue,
120    },
121    Video {
122        video: VideoValue,
123    },
124    LinkToPage {
125        link_to_page: Parent,
126    },
127    MeetingNotes {
128        meeting_notes: MeetingNotesValue,
129    },
130    Unsupported,
131}
132
133#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
134pub struct BookmarkValue {
135    pub caption: Vec<RichText>,
136    pub url: String,
137}
138
139#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
140pub struct BreadcrumpValue {}
141
142#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
143pub struct BulletedListItemValue {
144    pub rich_text: Vec<RichText>,
145    pub color: TextColor,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub children: Option<Vec<Block>>,
148}
149
150#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
151pub struct CalloutValue {
152    pub rich_text: Vec<RichText>,
153    pub icon: Option<Icon>,
154    pub color: TextColor,
155}
156
157#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
158pub struct ChildDatabaseValue {
159    pub title: String,
160}
161
162#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
163pub struct ChildPageValue {
164    pub title: String,
165}
166
167#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
168pub struct CodeValue {
169    pub caption: Vec<RichText>,
170    pub rich_text: Vec<RichText>,
171    pub language: Language,
172}
173
174#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
175pub struct ColumnListValue {}
176
177#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
178pub struct ColumnValue {}
179
180#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
181pub struct DividerValue {}
182
183#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
184pub struct EmbedValue {
185    pub url: String,
186}
187
188#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
189pub struct EquationValue {
190    pub expression: String,
191}
192
193#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
194pub struct FileValue {
195    pub caption: Vec<RichText>,
196    #[serde(flatten)]
197    pub file_type: File,
198    pub name: String,
199}
200
201#[skip_serializing_none]
202#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
203pub struct HeadingsValue {
204    pub rich_text: Vec<RichText>,
205    pub color: Option<TextColor>,
206    pub is_toggleable: Option<bool>,
207}
208
209#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
210pub struct ImageValue {
211    #[serde(flatten)]
212    pub file_type: File,
213}
214
215#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
216pub struct LinkPreviewValue {
217    pub url: String,
218}
219
220#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
221pub struct NumberedListItemValue {
222    pub rich_text: Vec<RichText>,
223    pub color: TextColor,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub children: Option<Vec<Block>>,
226}
227
228#[skip_serializing_none]
229#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
230pub struct ParagraphValue {
231    pub rich_text: Vec<RichText>,
232    pub color: Option<TextColor>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub children: Option<Vec<Block>>,
235}
236
237#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
238pub struct PdfValue {
239    pub caption: Vec<RichText>,
240    #[serde(flatten)]
241    pub file_type: File,
242}
243
244#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
245pub struct QuoteValue {
246    pub rich_text: Vec<RichText>,
247    pub color: TextColor,
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub children: Option<Vec<Block>>,
250}
251
252#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
253pub struct SyncedBlockValue {
254    pub synced_from: Option<SyncedFrom>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub children: Option<Vec<Block>>,
257}
258
259#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
260#[serde(tag = "type", rename_all = "snake_case")]
261pub enum SyncedFrom {
262    BlockId { block_id: String },
263}
264
265#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
266pub struct TableValue {
267    pub table_width: u32,
268    pub has_column_header: bool,
269    pub has_row_header: bool,
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub children: Option<Vec<Block>>,
272}
273
274#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
275pub struct TableRowsValue {
276    pub cells: Vec<Vec<RichText>>,
277}
278
279#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
280pub struct TableOfContentsValue {
281    pub color: TextColor,
282}
283
284#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
285pub struct TemplateValue {
286    pub rich_text: Vec<RichText>,
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub children: Option<Vec<Block>>,
289}
290
291#[skip_serializing_none]
292#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
293pub struct ToDoValue {
294    pub rich_text: Vec<RichText>,
295    pub checked: Option<bool>,
296    pub color: Option<TextColor>,
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub children: Option<Vec<Block>>,
299}
300
301#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
302pub struct ToggleValue {
303    pub rich_text: Vec<RichText>,
304    pub color: TextColor,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub children: Option<Vec<Block>>,
307}
308
309#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
310pub struct VideoValue {
311    #[serde(flatten)]
312    pub file_type: File,
313}
314
315#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
316pub struct MeetingNotesValue {}
317
318#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
319#[serde(rename_all = "snake_case")]
320pub enum TextColor {
321    Blue,
322    BlueBackground,
323    Brown,
324    BrownBackground,
325    Default,
326    Gray,
327    GrayBackground,
328    Green,
329    GreenBackground,
330    Orange,
331    OrangeBackground,
332    Yellow,
333    YellowBackground,
334    Pink,
335    PinkBackground,
336    Purple,
337    PurpleBackground,
338    Red,
339    RedBackground,
340}
341
342#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
343#[serde(rename_all = "snake_case", untagged)]
344pub enum Icon {
345    File(File),
346    Emoji(Emoji),
347    NativeIcon(NativeIcon),
348}
349
350#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
351#[serde(rename_all = "lowercase")]
352pub enum Language {
353    Abap,
354    Arduino,
355    Bash,
356    Basic,
357    C,
358    Clojure,
359    Coffeescript,
360    #[serde(rename = "c++")]
361    CPlusPlus,
362    #[serde(rename = "c#")]
363    CSharp,
364    Css,
365    Dart,
366    Diff,
367    Docker,
368    Elixir,
369    Elm,
370    Erlang,
371    Flow,
372    Fortran,
373    #[serde(rename = "f#")]
374    FSharp,
375    Gherkin,
376    Glsl,
377    Go,
378    Graphql,
379    Groovy,
380    Haskell,
381    Html,
382    Java,
383    Javascript,
384    Json,
385    Julia,
386    Kotlin,
387    Latex,
388    Less,
389    Lisp,
390    Livescript,
391    Lua,
392    Makefile,
393    Markdown,
394    Markup,
395    Matlab,
396    Mermaid,
397    Nix,
398    #[serde(rename = "objective-c")]
399    ObjectiveC,
400    Ocaml,
401    Pascal,
402    Perl,
403    Php,
404    #[serde(rename = "plain text")]
405    PlainText,
406    Powershell,
407    Prolog,
408    Protobuf,
409    Python,
410    R,
411    Reason,
412    Ruby,
413    Rust,
414    Sass,
415    Scala,
416    Scheme,
417    Scss,
418    Shell,
419    Sql,
420    Swift,
421    Solidity,
422    Typescript,
423    #[serde(rename = "vb.net")]
424    VbNet,
425    Verilog,
426    Vhdl,
427    #[serde(rename = "visual basic")]
428    VisualBasic,
429    Webassembly,
430    Xml,
431    Yaml,
432    #[serde(rename = "java/c/c++/c#")]
433    JavaOrCOrCPlusPlusOrCSharp,
434}
435
436impl BlockType {
437    pub fn plain_text(&self) -> Vec<Option<String>> {
438        match self {
439            BlockType::None => vec![],
440            BlockType::Bookmark { bookmark } => {
441                bookmark.caption.iter().map(|rt| rt.plain_text()).collect()
442            }
443            BlockType::Breadcrumb { breadcrump: _ } => vec![],
444            BlockType::BulletedListItem { bulleted_list_item } => {
445                let mut items = bulleted_list_item
446                    .rich_text
447                    .iter()
448                    .map(|rt| rt.plain_text())
449                    .collect();
450                let children = &bulleted_list_item.children;
451                let Some(children) = children else {
452                    return items;
453                };
454                items.append(
455                    &mut children
456                        .iter()
457                        .flat_map(|b| b.block_type.plain_text())
458                        .collect(),
459                );
460                items
461            }
462            BlockType::Callout { callout } => {
463                callout.rich_text.iter().map(|rt| rt.plain_text()).collect()
464            }
465            BlockType::ChildDatabase { child_database } => vec![Some(child_database.title.clone())],
466            BlockType::ChildPage { child_page } => vec![Some(child_page.title.clone())],
467            BlockType::Code { code } => code.caption.iter().map(|rt| rt.plain_text()).collect(),
468            BlockType::ColumnList { column_list: _ } => vec![],
469            BlockType::Column { column: _ } => vec![],
470            BlockType::Divider { divider: _ } => vec![],
471            BlockType::Embed { embed: _ } => vec![],
472            BlockType::Equation { equation: _ } => vec![],
473            BlockType::File { file } => file.caption.iter().map(|rt| rt.plain_text()).collect(),
474            BlockType::Heading1 { heading_1 } => heading_1
475                .rich_text
476                .iter()
477                .map(|rt| rt.plain_text())
478                .collect(),
479            BlockType::Heading2 { heading_2 } => heading_2
480                .rich_text
481                .iter()
482                .map(|rt| rt.plain_text())
483                .collect(),
484            BlockType::Heading3 { heading_3 } => heading_3
485                .rich_text
486                .iter()
487                .map(|rt| rt.plain_text())
488                .collect(),
489            BlockType::Image { image: _ } => vec![],
490            BlockType::LinkPreview { link_preview: _ } => vec![],
491            BlockType::NumberedListItem { numbered_list_item } => {
492                let mut items = numbered_list_item
493                    .rich_text
494                    .iter()
495                    .map(|rt| rt.plain_text())
496                    .collect();
497                let children = &numbered_list_item.children;
498                let Some(children) = children else {
499                    return items;
500                };
501                items.append(
502                    &mut children
503                        .iter()
504                        .flat_map(|b| b.block_type.plain_text())
505                        .collect(),
506                );
507                items
508            }
509            BlockType::Paragraph { paragraph } => {
510                let mut items = paragraph
511                    .rich_text
512                    .iter()
513                    .map(|rt| rt.plain_text())
514                    .collect();
515                let children = &paragraph.children;
516                let Some(children) = children else {
517                    return items;
518                };
519                items.append(
520                    &mut children
521                        .iter()
522                        .flat_map(|b| b.block_type.plain_text())
523                        .collect(),
524                );
525                items
526            }
527            BlockType::Pdf { pdf } => pdf.caption.iter().map(|rt| rt.plain_text()).collect(),
528            BlockType::Quote { quote } => {
529                let mut items = quote.rich_text.iter().map(|rt| rt.plain_text()).collect();
530                let children = &quote.children;
531                let Some(children) = children else {
532                    return items;
533                };
534                items.append(
535                    &mut children
536                        .iter()
537                        .flat_map(|b| b.block_type.plain_text())
538                        .collect(),
539                );
540                items
541            }
542            BlockType::SyncedBlock { synced_block } => {
543                let Some(children) = &synced_block.children else {
544                    return vec![];
545                };
546                children
547                    .iter()
548                    .flat_map(|b| b.block_type.plain_text())
549                    .collect()
550            }
551            BlockType::Table { table } => {
552                let Some(children) = &table.children else {
553                    return vec![];
554                };
555                children
556                    .iter()
557                    .flat_map(|b| b.block_type.plain_text())
558                    .collect()
559            }
560            BlockType::TableOfContents {
561                table_of_contents: _,
562            } => vec![],
563            BlockType::TableRow { table_row } => table_row
564                .cells
565                .iter()
566                .flatten()
567                .map(|rt| rt.plain_text())
568                .collect(),
569            BlockType::Template { template } => {
570                let mut items = template
571                    .rich_text
572                    .iter()
573                    .map(|rt| rt.plain_text())
574                    .collect();
575                let children = &template.children;
576                let Some(children) = children else {
577                    return items;
578                };
579                items.append(
580                    &mut children
581                        .iter()
582                        .flat_map(|b| b.block_type.plain_text())
583                        .collect(),
584                );
585                items
586            }
587            BlockType::ToDo { to_do } => {
588                let mut items = to_do.rich_text.iter().map(|rt| rt.plain_text()).collect();
589                let children = &to_do.children;
590                let Some(children) = children else {
591                    return items;
592                };
593                items.append(
594                    &mut children
595                        .iter()
596                        .flat_map(|b| b.block_type.plain_text())
597                        .collect(),
598                );
599                items
600            }
601            BlockType::Toggle { toggle } => {
602                let mut items = toggle.rich_text.iter().map(|rt| rt.plain_text()).collect();
603                let children = &toggle.children;
604                let Some(children) = children else {
605                    return items;
606                };
607                items.append(
608                    &mut children
609                        .iter()
610                        .flat_map(|b| b.block_type.plain_text())
611                        .collect(),
612                );
613                items
614            }
615            BlockType::Video { video: _ } => vec![],
616            BlockType::LinkToPage { link_to_page: _ } => vec![],
617            BlockType::MeetingNotes { meeting_notes: _ } => vec![],
618            BlockType::Unsupported => vec![],
619        }
620    }
621}