notion_client/objects/
block.rs

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