From 8c9f519e7362444f7c99b625c65c8cd419b28c0c Mon Sep 17 00:00:00 2001 From: David Stotijn Date: Fri, 12 Aug 2022 19:49:36 +0200 Subject: [PATCH] Convert `Block` from a struct to an interface (#27) --- block.go | 875 +++++++++++++++++++++++++++++++++++++++++++++---- client.go | 52 +-- client_test.go | 454 +++++++++++++++---------- 3 files changed, 1118 insertions(+), 263 deletions(-) diff --git a/block.go b/block.go index 142d809..70e14d9 100644 --- a/block.go +++ b/block.go @@ -2,13 +2,22 @@ package notion import ( "encoding/json" + "fmt" "time" ) // Block represents content on the Notion platform. // See: https://developers.notion.com/reference/block -type Block struct { - Object string `json:"object"` +type Block interface { + ID() string + CreatedTime() time.Time + LastEditedTime() time.Time + HasChildren() bool + Archived() bool + json.Marshaler +} + +type blockDTO struct { ID string `json:"id,omitempty"` Type BlockType `json:"type,omitempty"` CreatedTime *time.Time `json:"created_time,omitempty"` @@ -16,113 +25,640 @@ type Block struct { HasChildren bool `json:"has_children,omitempty"` Archived *bool `json:"archived,omitempty"` - Paragraph *RichTextBlock `json:"paragraph,omitempty"` - Heading1 *Heading `json:"heading_1,omitempty"` - Heading2 *Heading `json:"heading_2,omitempty"` - Heading3 *Heading `json:"heading_3,omitempty"` - BulletedListItem *RichTextBlock `json:"bulleted_list_item,omitempty"` - NumberedListItem *RichTextBlock `json:"numbered_list_item,omitempty"` - ToDo *ToDo `json:"to_do,omitempty"` - Toggle *RichTextBlock `json:"toggle,omitempty"` - ChildPage *ChildPage `json:"child_page,omitempty"` - ChildDatabase *ChildDatabase `json:"child_database,omitempty"` - Callout *Callout `json:"callout,omitempty"` - Quote *RichTextBlock `json:"quote,omitempty"` - Code *Code `json:"code,omitempty"` - Embed *Embed `json:"embed,omitempty"` - Image *FileBlock `json:"image,omitempty"` - Video *FileBlock `json:"video,omitempty"` - File *FileBlock `json:"file,omitempty"` - PDF *FileBlock `json:"pdf,omitempty"` - Bookmark *Bookmark `json:"bookmark,omitempty"` - Equation *Equation `json:"equation,omitempty"` - Divider *Divider `json:"divider,omitempty"` - TableOfContents *TableOfContents `json:"table_of_contents,omitempty"` - Breadcrumb *Breadcrumb `json:"breadcrumb,omitempty"` - ColumnList *ColumnList `json:"column_list,omitempty"` - Column *Column `json:"column,omitempty"` - Table *Table `json:"table,omitempty"` - TableRow *TableRow `json:"table_row,omitempty"` - LinkPreview *LinkPreview `json:"link_preview,omitempty"` - LinkToPage *LinkToPage `json:"link_to_page,omitempty"` - SyncedBlock *SyncedBlock `json:"synced_block,omitempty"` - Template *RichTextBlock `json:"template,omitempty"` + Paragraph *ParagraphBlock `json:"paragraph,omitempty"` + Heading1 *Heading1Block `json:"heading_1,omitempty"` + Heading2 *Heading2Block `json:"heading_2,omitempty"` + Heading3 *Heading3Block `json:"heading_3,omitempty"` + BulletedListItem *BulletedListItemBlock `json:"bulleted_list_item,omitempty"` + NumberedListItem *NumberedListItemBlock `json:"numbered_list_item,omitempty"` + ToDo *ToDoBlock `json:"to_do,omitempty"` + Toggle *ToggleBlock `json:"toggle,omitempty"` + ChildPage *ChildPageBlock `json:"child_page,omitempty"` + ChildDatabase *ChildDatabaseBlock `json:"child_database,omitempty"` + Callout *CalloutBlock `json:"callout,omitempty"` + Quote *QuoteBlock `json:"quote,omitempty"` + Code *CodeBlock `json:"code,omitempty"` + Embed *EmbedBlock `json:"embed,omitempty"` + Image *ImageBlock `json:"image,omitempty"` + Video *VideoBlock `json:"video,omitempty"` + File *FileBlock `json:"file,omitempty"` + PDF *PDFBlock `json:"pdf,omitempty"` + Bookmark *BookmarkBlock `json:"bookmark,omitempty"` + Equation *EquationBlock `json:"equation,omitempty"` + Divider *DividerBlock `json:"divider,omitempty"` + TableOfContents *TableOfContentsBlock `json:"table_of_contents,omitempty"` + Breadcrumb *BreadcrumbBlock `json:"breadcrumb,omitempty"` + ColumnList *ColumnListBlock `json:"column_list,omitempty"` + Column *ColumnBlock `json:"column,omitempty"` + Table *TableBlock `json:"table,omitempty"` + TableRow *TableRowBlock `json:"table_row,omitempty"` + LinkPreview *LinkPreviewBlock `json:"link_preview,omitempty"` + LinkToPage *LinkToPageBlock `json:"link_to_page,omitempty"` + SyncedBlock *SyncedBlock `json:"synced_block,omitempty"` + Template *TemplateBlock `json:"template,omitempty"` } -type RichTextBlock struct { +type baseBlock struct { + id string + createdTime time.Time + lastEditedTime time.Time + hasChildren bool + archived bool +} + +// ID returns the identifier (UUIDv4) for the block. +func (b baseBlock) ID() string { + return b.id +} + +func (b baseBlock) CreatedTime() time.Time { + return b.createdTime +} + +func (b baseBlock) LastEditedTime() time.Time { + return b.lastEditedTime +} + +func (b baseBlock) HasChildren() bool { + return b.hasChildren +} + +func (b baseBlock) Archived() bool { + return b.archived +} + +type ParagraphBlock struct { + baseBlock + Text []RichText `json:"text"` Children []Block `json:"children,omitempty"` } -type Heading struct { - Text []RichText `json:"text"` +// MarshalJSON implements json.Marshaler. +func (b ParagraphBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ParagraphBlock + dto struct { + Paragraph blockAlias `json:"paragraph"` + } + ) + + return json.Marshal(dto{ + Paragraph: blockAlias(b), + }) } -type ToDo struct { - RichTextBlock - Checked *bool `json:"checked,omitempty"` +type BulletedListItemBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` } -type ChildPage struct { +// MarshalJSON implements json.Marshaler. +func (b BulletedListItemBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias BulletedListItemBlock + dto struct { + BulletedListItem blockAlias `json:"bulleted_list_item"` + } + ) + + return json.Marshal(dto{ + BulletedListItem: blockAlias(b), + }) +} + +type NumberedListItemBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b NumberedListItemBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias NumberedListItemBlock + dto struct { + NumberedListItem blockAlias `json:"numbered_list_item"` + } + ) + + return json.Marshal(dto{ + NumberedListItem: blockAlias(b), + }) +} + +type QuoteBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b QuoteBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias QuoteBlock + dto struct { + Quote blockAlias `json:"quote"` + } + ) + + return json.Marshal(dto{ + Quote: blockAlias(b), + }) +} + +type ToggleBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b ToggleBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ToggleBlock + dto struct { + Toggle blockAlias `json:"toggle"` + } + ) + + return json.Marshal(dto{ + Toggle: blockAlias(b), + }) +} + +type TemplateBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b TemplateBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias TemplateBlock + dto struct { + Template blockAlias `json:"template"` + } + ) + + return json.Marshal(dto{ + Template: blockAlias(b), + }) +} + +type Heading1Block struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b Heading1Block) MarshalJSON() ([]byte, error) { + type ( + blockAlias Heading1Block + dto struct { + Heading1 blockAlias `json:"heading_1"` + } + ) + + return json.Marshal(dto{ + Heading1: blockAlias(b), + }) +} + +type Heading2Block struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b Heading2Block) MarshalJSON() ([]byte, error) { + type ( + blockAlias Heading2Block + dto struct { + Heading2 blockAlias `json:"heading_2"` + } + ) + + return json.Marshal(dto{ + Heading2: blockAlias(b), + }) +} + +type Heading3Block struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b Heading3Block) MarshalJSON() ([]byte, error) { + type ( + blockAlias Heading3Block + dto struct { + Heading3 blockAlias `json:"heading_3"` + } + ) + + return json.Marshal(dto{ + Heading3: blockAlias(b), + }) +} + +type ToDoBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` + Checked *bool `json:"checked,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b ToDoBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ToDoBlock + dto struct { + ToDo blockAlias `json:"to_do"` + } + ) + + return json.Marshal(dto{ + ToDo: blockAlias(b), + }) +} + +type ChildPageBlock struct { + baseBlock + Title string `json:"title"` } -type ChildDatabase struct { +// MarshalJSON implements json.Marshaler. +func (b ChildPageBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ChildPageBlock + dto struct { + ChildPage blockAlias `json:"child_page"` + } + ) + + return json.Marshal(dto{ + ChildPage: blockAlias(b), + }) +} + +type ChildDatabaseBlock struct { + baseBlock + Title string `json:"title"` } -type Callout struct { - RichTextBlock - Icon *Icon `json:"icon,omitempty"` +// MarshalJSON implements json.Marshaler. +func (b ChildDatabaseBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ChildDatabaseBlock + dto struct { + ChildDatabase blockAlias `json:"child_database"` + } + ) + + return json.Marshal(dto{ + ChildDatabase: blockAlias(b), + }) } -type Code struct { - RichTextBlock +type CalloutBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` + Icon *Icon `json:"icon,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b CalloutBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias CalloutBlock + dto struct { + Callout blockAlias `json:"callout"` + } + ) + + return json.Marshal(dto{ + Callout: blockAlias(b), + }) +} + +type CodeBlock struct { + baseBlock + + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` Caption []RichText `json:"caption,omitempty"` Language *string `json:"language,omitempty"` } -type Embed struct { +// MarshalJSON implements json.Marshaler. +func (b CodeBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias CodeBlock + dto struct { + Code blockAlias `json:"code"` + } + ) + + return json.Marshal(dto{ + Code: blockAlias(b), + }) +} + +type EmbedBlock struct { + baseBlock + URL string `json:"url"` } -type FileBlock struct { - Type FileType `json:"type"` +// MarshalJSON implements json.Marshaler. +func (b EmbedBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias EmbedBlock + dto struct { + Embed blockAlias `json:"embed"` + } + ) + return json.Marshal(dto{ + Embed: blockAlias(b), + }) +} + +type ImageBlock struct { + baseBlock + + Type FileType `json:"type"` File *FileFile `json:"file,omitempty"` External *FileExternal `json:"external,omitempty"` Caption []RichText `json:"caption,omitempty"` } -type Bookmark struct { +// MarshalJSON implements json.Marshaler. +func (b ImageBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ImageBlock + dto struct { + Image blockAlias `json:"image"` + } + ) + + return json.Marshal(dto{ + Image: blockAlias(b), + }) +} + +type VideoBlock struct { + baseBlock + + Type FileType `json:"type"` + File *FileFile `json:"file,omitempty"` + External *FileExternal `json:"external,omitempty"` + Caption []RichText `json:"caption,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b VideoBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias VideoBlock + dto struct { + Video blockAlias `json:"video"` + } + ) + + return json.Marshal(dto{ + Video: blockAlias(b), + }) +} + +type FileBlock struct { + baseBlock + + Type FileType `json:"type"` + File *FileFile `json:"file,omitempty"` + External *FileExternal `json:"external,omitempty"` + Caption []RichText `json:"caption,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b FileBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias FileBlock + dto struct { + File blockAlias `json:"file"` + } + ) + + return json.Marshal(dto{ + File: blockAlias(b), + }) +} + +type PDFBlock struct { + baseBlock + + Type FileType `json:"type"` + File *FileFile `json:"file,omitempty"` + External *FileExternal `json:"external,omitempty"` + Caption []RichText `json:"caption,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b PDFBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias PDFBlock + dto struct { + PDF blockAlias `json:"pdf"` + } + ) + + return json.Marshal(dto{ + PDF: blockAlias(b), + }) +} + +type BookmarkBlock struct { + baseBlock + URL string `json:"url"` Caption []RichText `json:"caption,omitempty"` } -type ColumnList struct { +// MarshalJSON implements json.Marshaler. +func (b BookmarkBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias BookmarkBlock + dto struct { + Bookmark blockAlias `json:"bookmark"` + } + ) + + return json.Marshal(dto{ + Bookmark: blockAlias(b), + }) +} + +type EquationBlock struct { + baseBlock + + Expression string `json:"expression"` +} + +// MarshalJSON implements json.Marshaler. +func (b EquationBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias EquationBlock + dto struct { + Equation blockAlias `json:"equation"` + } + ) + + return json.Marshal(dto{ + Equation: blockAlias(b), + }) +} + +type ColumnListBlock struct { + baseBlock + Children []Block `json:"children,omitempty"` } -type Column struct { +// MarshalJSON implements json.Marshaler. +func (b ColumnListBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ColumnListBlock + dto struct { + ColumnList blockAlias `json:"column_list"` + } + ) + + return json.Marshal(dto{ + ColumnList: blockAlias(b), + }) +} + +type ColumnBlock struct { + baseBlock + Children []Block `json:"children,omitempty"` } -type Table struct { +// MarshalJSON implements json.Marshaler. +func (b ColumnBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias ColumnBlock + dto struct { + Column blockAlias `json:"column"` + } + ) + + return json.Marshal(dto{ + Column: blockAlias(b), + }) +} + +type TableBlock struct { + baseBlock + TableWidth int `json:"table_width"` HasColumnHeader bool `json:"has_column_header"` HasRowHeader bool `json:"has_row_header"` Children []Block `json:"children,omitempty"` } -type TableRow struct { +// MarshalJSON implements json.Marshaler. +func (b TableBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias TableBlock + dto struct { + Table blockAlias `json:"table"` + } + ) + + return json.Marshal(dto{ + Table: blockAlias(b), + }) +} + +type TableRowBlock struct { + baseBlock + Cells [][]RichText `json:"cells"` } -type LinkToPage struct { - Type LinkToPageType `json:"type"` +// MarshalJSON implements json.Marshaler. +func (b TableRowBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias TableRowBlock + dto struct { + TableRow blockAlias `json:"table_row"` + } + ) - PageID string `json:"page_id,omitempty"` - DatabaseID string `json:"database_id,omitempty"` + return json.Marshal(dto{ + TableRow: blockAlias(b), + }) +} + +type LinkPreviewBlock struct { + baseBlock + + URL string `json:"url"` +} + +// MarshalJSON implements json.Marshaler. +func (b LinkPreviewBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias LinkPreviewBlock + dto struct { + LinkPreview blockAlias `json:"link_preview"` + } + ) + + return json.Marshal(dto{ + LinkPreview: blockAlias(b), + }) +} + +type LinkToPageBlock struct { + baseBlock + + Type LinkToPageType `json:"type"` + PageID string `json:"page_id,omitempty"` + DatabaseID string `json:"database_id,omitempty"` +} + +// MarshalJSON implements json.Marshaler. +func (b LinkToPageBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias LinkToPageBlock + dto struct { + LinkToPage blockAlias `json:"link_to_page"` + } + ) + + return json.Marshal(dto{ + LinkToPage: blockAlias(b), + }) } type LinkToPageType string @@ -133,10 +669,26 @@ const ( ) type SyncedBlock struct { + baseBlock + SyncedFrom *SyncedFrom `json:"synced_from"` Children []Block `json:"children,omitempty"` } +// MarshalJSON implements json.Marshaler. +func (b SyncedBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias SyncedBlock + dto struct { + SyncedBlock blockAlias `json:"synced_block"` + } + ) + + return json.Marshal(dto{ + SyncedBlock: blockAlias(b), + }) +} + type SyncedFrom struct { Type SyncedFromType `json:"type"` BlockID string `json:"block_id"` @@ -146,11 +698,59 @@ type SyncedFromType string const SyncedFromTypeBlockID SyncedFromType = "block_id" -type ( - Divider struct{} - TableOfContents struct{} - Breadcrumb struct{} -) +type DividerBlock struct { + baseBlock +} + +// MarshalJSON implements json.Marshaler. +func (b DividerBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias DividerBlock + dto struct { + Divider blockAlias `json:"divider"` + } + ) + + return json.Marshal(dto{ + Divider: blockAlias(b), + }) +} + +type TableOfContentsBlock struct { + baseBlock +} + +// MarshalJSON implements json.Marshaler. +func (b TableOfContentsBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias TableOfContentsBlock + dto struct { + TableOfContents blockAlias `json:"table_of_contents"` + } + ) + + return json.Marshal(dto{ + TableOfContents: blockAlias(b), + }) +} + +type BreadcrumbBlock struct { + baseBlock +} + +// MarshalJSON implements json.Marshaler. +func (b BreadcrumbBlock) MarshalJSON() ([]byte, error) { + type ( + blockAlias BreadcrumbBlock + dto struct { + Breadcrumb blockAlias `json:"breadcrumb"` + } + ) + + return json.Marshal(dto{ + Breadcrumb: blockAlias(b), + }) +} type BlockType string @@ -196,17 +796,148 @@ type PaginationQuery struct { // BlockChildrenResponse contains results (block children) and pagination data returned from a find request. type BlockChildrenResponse struct { - Results []Block `json:"results"` - HasMore bool `json:"has_more"` - NextCursor *string `json:"next_cursor"` + Results []Block + HasMore bool + NextCursor *string } -// MarshalJSON implements json.Marshaler. -func (b Block) MarshalJSON() ([]byte, error) { - type blockAlias Block +func (resp *BlockChildrenResponse) UnmarshalJSON(b []byte) error { + type responseDTO struct { + Results []blockDTO `json:"results"` + HasMore bool `json:"has_more"` + NextCursor *string `json:"next_cursor"` + } - alias := blockAlias(b) - alias.Object = "block" + var dto responseDTO - return json.Marshal(alias) + if err := json.Unmarshal(b, &dto); err != nil { + return err + } + + resp.HasMore = dto.HasMore + resp.NextCursor = dto.NextCursor + resp.Results = make([]Block, len(dto.Results)) + + for i, blockDTO := range dto.Results { + resp.Results[i] = blockDTO.Block() + } + + return nil +} + +func (dto blockDTO) Block() Block { + baseBlock := baseBlock{ + id: dto.ID, + hasChildren: dto.HasChildren, + } + + if dto.CreatedTime != nil { + baseBlock.createdTime = *dto.CreatedTime + } + + if dto.LastEditedTime != nil { + baseBlock.lastEditedTime = *dto.LastEditedTime + } + + if dto.Archived != nil { + baseBlock.archived = *dto.Archived + } + + switch dto.Type { + case BlockTypeParagraph: + dto.Paragraph.baseBlock = baseBlock + return dto.Paragraph + case BlockTypeHeading1: + dto.Heading1.baseBlock = baseBlock + return dto.Heading1 + case BlockTypeHeading2: + dto.Heading2.baseBlock = baseBlock + return dto.Heading2 + case BlockTypeHeading3: + dto.Heading3.baseBlock = baseBlock + return dto.Heading3 + case BlockTypeBulletedListItem: + dto.BulletedListItem.baseBlock = baseBlock + return dto.BulletedListItem + case BlockTypeNumberedListItem: + dto.NumberedListItem.baseBlock = baseBlock + return dto.NumberedListItem + case BlockTypeToDo: + dto.ToDo.baseBlock = baseBlock + return dto.ToDo + case BlockTypeToggle: + dto.Toggle.baseBlock = baseBlock + return dto.Toggle + case BlockTypeChildPage: + dto.ChildPage.baseBlock = baseBlock + return dto.ChildPage + case BlockTypeChildDatabase: + dto.ChildDatabase.baseBlock = baseBlock + return dto.ChildDatabase + case BlockTypeCallout: + dto.Callout.baseBlock = baseBlock + return dto.Callout + case BlockTypeQuote: + dto.Quote.baseBlock = baseBlock + return dto.Quote + case BlockTypeCode: + dto.Code.baseBlock = baseBlock + return dto.Code + case BlockTypeEmbed: + dto.Embed.baseBlock = baseBlock + return dto.Embed + case BlockTypeImage: + dto.Image.baseBlock = baseBlock + return dto.Image + case BlockTypeVideo: + dto.Video.baseBlock = baseBlock + return dto.Video + case BlockTypeFile: + dto.File.baseBlock = baseBlock + return dto.File + case BlockTypePDF: + dto.PDF.baseBlock = baseBlock + return dto.PDF + case BlockTypeBookmark: + dto.Bookmark.baseBlock = baseBlock + return dto.Bookmark + case BlockTypeEquation: + dto.Equation.baseBlock = baseBlock + return dto.Equation + case BlockTypeDivider: + dto.Divider.baseBlock = baseBlock + return dto.Divider + case BlockTypeTableOfContents: + dto.TableOfContents.baseBlock = baseBlock + return dto.TableOfContents + case BlockTypeBreadCrumb: + dto.Breadcrumb.baseBlock = baseBlock + return dto.Breadcrumb + case BlockTypeColumnList: + dto.ColumnList.baseBlock = baseBlock + return dto.ColumnList + case BlockTypeColumn: + dto.Column.baseBlock = baseBlock + return dto.Column + case BlockTypeTable: + dto.Table.baseBlock = baseBlock + return dto.Table + case BlockTypeTableRow: + dto.TableRow.baseBlock = baseBlock + return dto.TableRow + case BlockTypeLinkPreview: + dto.LinkPreview.baseBlock = baseBlock + return dto.LinkPreview + case BlockTypeLinkToPage: + dto.LinkToPage.baseBlock = baseBlock + return dto.LinkToPage + case BlockTypeSyncedBlock: + dto.SyncedBlock.baseBlock = baseBlock + return dto.SyncedBlock + case BlockTypeTemplate: + dto.Template.baseBlock = baseBlock + return dto.Template + default: + panic(fmt.Sprintf("type %q is unsupported", dto.Type)) + } } diff --git a/client.go b/client.go index 125eea1..b67a808 100644 --- a/client.go +++ b/client.go @@ -413,87 +413,93 @@ func (c *Client) AppendBlockChildren(ctx context.Context, blockID string, childr // FindBlockByID returns a single of block for a given block ID. // See: https://developers.notion.com/reference/retrieve-a-block -func (c *Client) FindBlockByID(ctx context.Context, blockID string) (block Block, err error) { +func (c *Client) FindBlockByID(ctx context.Context, blockID string) (Block, error) { req, err := c.newRequest(ctx, http.MethodGet, fmt.Sprintf("/blocks/%v", blockID), nil) if err != nil { - return Block{}, fmt.Errorf("notion: invalid request: %w", err) + return nil, fmt.Errorf("notion: invalid request: %w", err) } res, err := c.httpClient.Do(req) if err != nil { - return Block{}, fmt.Errorf("notion: failed to make HTTP request: %w", err) + return nil, fmt.Errorf("notion: failed to make HTTP request: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return Block{}, fmt.Errorf("notion: failed to find block: %w", parseErrorResponse(res)) + return nil, fmt.Errorf("notion: failed to find block: %w", parseErrorResponse(res)) } - err = json.NewDecoder(res.Body).Decode(&block) + var dto blockDTO + + err = json.NewDecoder(res.Body).Decode(&dto) if err != nil { - return Block{}, fmt.Errorf("notion: failed to parse HTTP response: %w", err) + return nil, fmt.Errorf("notion: failed to parse HTTP response: %w", err) } - return block, nil + return dto.Block(), nil } // UpdateBlock updates a block. // See: https://developers.notion.com/reference/update-a-block -func (c *Client) UpdateBlock(ctx context.Context, blockID string, block Block) (updatedBlock Block, err error) { +func (c *Client) UpdateBlock(ctx context.Context, blockID string, block Block) (Block, error) { body := &bytes.Buffer{} - err = json.NewEncoder(body).Encode(block) + err := json.NewEncoder(body).Encode(block) if err != nil { - return Block{}, fmt.Errorf("notion: failed to encode body params to JSON: %w", err) + return nil, fmt.Errorf("notion: failed to encode body params to JSON: %w", err) } req, err := c.newRequest(ctx, http.MethodPatch, "/blocks/"+blockID, body) if err != nil { - return Block{}, fmt.Errorf("notion: invalid request: %w", err) + return nil, fmt.Errorf("notion: invalid request: %w", err) } res, err := c.httpClient.Do(req) if err != nil { - return Block{}, fmt.Errorf("notion: failed to make HTTP request: %w", err) + return nil, fmt.Errorf("notion: failed to make HTTP request: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return Block{}, fmt.Errorf("notion: failed to update block: %w", parseErrorResponse(res)) + return nil, fmt.Errorf("notion: failed to update block: %w", parseErrorResponse(res)) } - err = json.NewDecoder(res.Body).Decode(&updatedBlock) + var dto blockDTO + + err = json.NewDecoder(res.Body).Decode(&dto) if err != nil { - return Block{}, fmt.Errorf("notion: failed to parse HTTP response: %w", err) + return nil, fmt.Errorf("notion: failed to parse HTTP response: %w", err) } - return updatedBlock, nil + return dto.Block(), nil } // DeleteBlock sets `archived: true` on a (page) block object. // See: https://developers.notion.com/reference/delete-a-block -func (c *Client) DeleteBlock(ctx context.Context, blockID string) (deletedBlock Block, err error) { +func (c *Client) DeleteBlock(ctx context.Context, blockID string) (Block, error) { req, err := c.newRequest(ctx, http.MethodDelete, "/blocks/"+blockID, nil) if err != nil { - return Block{}, fmt.Errorf("notion: invalid request: %w", err) + return nil, fmt.Errorf("notion: invalid request: %w", err) } res, err := c.httpClient.Do(req) if err != nil { - return Block{}, fmt.Errorf("notion: failed to make HTTP request: %w", err) + return nil, fmt.Errorf("notion: failed to make HTTP request: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return Block{}, fmt.Errorf("notion: failed to delete block: %w", parseErrorResponse(res)) + return nil, fmt.Errorf("notion: failed to delete block: %w", parseErrorResponse(res)) } - err = json.NewDecoder(res.Body).Decode(&deletedBlock) + var dto blockDTO + + err = json.NewDecoder(res.Body).Decode(&dto) if err != nil { - return Block{}, fmt.Errorf("notion: failed to parse HTTP response: %w", err) + return nil, fmt.Errorf("notion: failed to parse HTTP response: %w", err) } - return deletedBlock, nil + return dto.Block(), nil } // FindUserByID fetches a user by ID. diff --git a/client_test.go b/client_test.go index 9392c88..6ac0518 100644 --- a/client_test.go +++ b/client_test.go @@ -14,6 +14,7 @@ import ( "github.com/dstotijn/go-notion" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) type mockRoundtripper struct { @@ -1667,15 +1668,11 @@ func TestCreatePage(t *testing.T) { }, }, Children: []notion.Block{ - { - Object: "block", - Type: notion.BlockTypeParagraph, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Text: ¬ion.Text{ - Content: "Lorem ipsum dolor sit amet.", - }, + ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Text: ¬ion.Text{ + Content: "Lorem ipsum dolor sit amet.", }, }, }, @@ -1763,8 +1760,6 @@ func TestCreatePage(t *testing.T) { }, "children": []interface{}{ map[string]interface{}{ - "object": "block", - "type": "paragraph", "paragraph": map[string]interface{}{ "text": []interface{}{ map[string]interface{}{ @@ -1846,15 +1841,11 @@ func TestCreatePage(t *testing.T) { }, }, Children: []notion.Block{ - { - Object: "block", - Type: notion.BlockTypeParagraph, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Text: ¬ion.Text{ - Content: "Lorem ipsum dolor sit amet.", - }, + ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Text: ¬ion.Text{ + Content: "Lorem ipsum dolor sit amet.", }, }, }, @@ -1916,8 +1907,6 @@ func TestCreatePage(t *testing.T) { }, "children": []interface{}{ map[string]interface{}{ - "object": "block", - "type": "paragraph", "paragraph": map[string]interface{}{ "text": []interface{}{ map[string]interface{}{ @@ -2877,6 +2866,14 @@ func TestFindPagePropertyByID(t *testing.T) { func TestFindBlockChildrenById(t *testing.T) { t.Parallel() + type blockFields struct { + id string + createdTime time.Time + lastEditedTime time.Time + hasChildren bool + archived bool + } + tests := []struct { name string query *notion.PaginationQuery @@ -2884,6 +2881,7 @@ func TestFindBlockChildrenById(t *testing.T) { respStatusCode int expQueryParams url.Values expResponse notion.BlockChildrenResponse + expBlockFields []blockFields expError error }{ { @@ -2939,24 +2937,17 @@ func TestFindBlockChildrenById(t *testing.T) { }, expResponse: notion.BlockChildrenResponse{ Results: []notion.Block{ - { - Object: "block", - ID: "ae9c9a31-1c1e-4ae2-a5ee-c539a2d43113", - CreatedTime: notion.TimePtr(mustParseTime(time.RFC3339Nano, "2021-05-14T09:15:00.000Z")), - LastEditedTime: notion.TimePtr(mustParseTime(time.RFC3339Nano, "2021-05-14T09:15:00.000Z")), - Type: notion.BlockTypeParagraph, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Type: notion.RichTextTypeText, - Text: ¬ion.Text{ - Content: "Lorem ipsum dolor sit amet.", - }, - Annotations: ¬ion.Annotations{ - Color: notion.ColorDefault, - }, - PlainText: "Lorem ipsum dolor sit amet.", + ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Type: notion.RichTextTypeText, + Text: ¬ion.Text{ + Content: "Lorem ipsum dolor sit amet.", }, + Annotations: ¬ion.Annotations{ + Color: notion.ColorDefault, + }, + PlainText: "Lorem ipsum dolor sit amet.", }, }, }, @@ -2964,6 +2955,15 @@ func TestFindBlockChildrenById(t *testing.T) { HasMore: true, NextCursor: notion.StringPtr("A^hd"), }, + expBlockFields: []blockFields{ + { + id: "ae9c9a31-1c1e-4ae2-a5ee-c539a2d43113", + createdTime: mustParseTime(time.RFC3339, "2021-05-14T09:15:00.000Z"), + lastEditedTime: mustParseTime(time.RFC3339, "2021-05-14T09:15:00.000Z"), + hasChildren: false, + archived: false, + }, + }, expError: nil, }, { @@ -3049,9 +3049,35 @@ func TestFindBlockChildrenById(t *testing.T) { t.Fatalf("error not equal (expected: %v, got: %v)", tt.expError, err) } - if diff := cmp.Diff(tt.expResponse, resp); diff != "" { + if diff := cmp.Diff(tt.expResponse, resp, cmpopts.IgnoreUnexported(notion.ParagraphBlock{})); diff != "" { t.Fatalf("response not equal (-exp, +got):\n%v", diff) } + + if len(tt.expBlockFields) != len(resp.Results) { + t.Fatalf("expected %v result(s), got %v", len(tt.expBlockFields), len(resp.Results)) + } + + for i, exp := range tt.expBlockFields { + if exp.id != resp.Results[i].ID() { + t.Fatalf("id not equal (expected: %v, got: %v)", exp.id, resp.Results[i].ID()) + } + + if exp.createdTime != resp.Results[i].CreatedTime() { + t.Fatalf("createdTime not equal (expected: %v, got: %v)", exp.createdTime, resp.Results[i].CreatedTime()) + } + + if exp.lastEditedTime != resp.Results[i].LastEditedTime() { + t.Fatalf("lastEditedTime not equal (expected: %v, got: %v)", exp.lastEditedTime, resp.Results[i].LastEditedTime()) + } + + if exp.hasChildren != resp.Results[i].HasChildren() { + t.Fatalf("hasChildren not equal (expected: %v, got: %v)", exp.hasChildren, resp.Results[i].HasChildren()) + } + + if exp.archived != resp.Results[i].Archived() { + t.Fatalf("archived not equal (expected: %v, got: %v)", exp.archived, resp.Results[i].Archived()) + } + } }) } } @@ -3059,6 +3085,14 @@ func TestFindBlockChildrenById(t *testing.T) { func TestAppendBlockChildren(t *testing.T) { t.Parallel() + type blockFields struct { + id string + createdTime time.Time + lastEditedTime time.Time + hasChildren bool + archived bool + } + tests := []struct { name string children []notion.Block @@ -3066,19 +3100,17 @@ func TestAppendBlockChildren(t *testing.T) { respStatusCode int expPostBody map[string]interface{} expResponse notion.BlockChildrenResponse + expBlockFields []blockFields expError error }{ { name: "successful response", children: []notion.Block{ - { - Type: notion.BlockTypeParagraph, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Text: ¬ion.Text{ - Content: "Lorem ipsum dolor sit amet.", - }, + ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Text: ¬ion.Text{ + Content: "Lorem ipsum dolor sit amet.", }, }, }, @@ -3128,8 +3160,6 @@ func TestAppendBlockChildren(t *testing.T) { expPostBody: map[string]interface{}{ "children": []interface{}{ map[string]interface{}{ - "object": "block", - "type": "paragraph", "paragraph": map[string]interface{}{ "text": []interface{}{ map[string]interface{}{ @@ -3144,24 +3174,17 @@ func TestAppendBlockChildren(t *testing.T) { }, expResponse: notion.BlockChildrenResponse{ Results: []notion.Block{ - { - Object: "block", - ID: "ae9c9a31-1c1e-4ae2-a5ee-c539a2d43113", - CreatedTime: notion.TimePtr(mustParseTime(time.RFC3339Nano, "2021-05-14T09:15:00.000Z")), - LastEditedTime: notion.TimePtr(mustParseTime(time.RFC3339Nano, "2021-05-14T09:15:00.000Z")), - Type: notion.BlockTypeParagraph, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Type: notion.RichTextTypeText, - Text: ¬ion.Text{ - Content: "Lorem ipsum dolor sit amet.", - }, - Annotations: ¬ion.Annotations{ - Color: notion.ColorDefault, - }, - PlainText: "Lorem ipsum dolor sit amet.", + ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Type: notion.RichTextTypeText, + Text: ¬ion.Text{ + Content: "Lorem ipsum dolor sit amet.", }, + Annotations: ¬ion.Annotations{ + Color: notion.ColorDefault, + }, + PlainText: "Lorem ipsum dolor sit amet.", }, }, }, @@ -3169,19 +3192,25 @@ func TestAppendBlockChildren(t *testing.T) { HasMore: true, NextCursor: notion.StringPtr("A^hd"), }, + expBlockFields: []blockFields{ + { + id: "ae9c9a31-1c1e-4ae2-a5ee-c539a2d43113", + createdTime: mustParseTime(time.RFC3339, "2021-05-14T09:15:00.000Z"), + lastEditedTime: mustParseTime(time.RFC3339, "2021-05-14T09:15:00.000Z"), + hasChildren: false, + archived: false, + }, + }, expError: nil, }, { name: "error response", children: []notion.Block{ - { - Type: notion.BlockTypeParagraph, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Text: ¬ion.Text{ - Content: "Lorem ipsum dolor sit amet.", - }, + ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Text: ¬ion.Text{ + Content: "Lorem ipsum dolor sit amet.", }, }, }, @@ -3201,8 +3230,6 @@ func TestAppendBlockChildren(t *testing.T) { expPostBody: map[string]interface{}{ "children": []interface{}{ map[string]interface{}{ - "object": "block", - "type": "paragraph", "paragraph": map[string]interface{}{ "text": []interface{}{ map[string]interface{}{ @@ -3268,9 +3295,35 @@ func TestAppendBlockChildren(t *testing.T) { t.Fatalf("error not equal (expected: %v, got: %v)", tt.expError, err) } - if diff := cmp.Diff(tt.expResponse, resp); diff != "" { + if diff := cmp.Diff(tt.expResponse, resp, cmpopts.IgnoreUnexported(notion.ParagraphBlock{})); diff != "" { t.Fatalf("response not equal (-exp, +got):\n%v", diff) } + + if len(tt.expBlockFields) != len(resp.Results) { + t.Fatalf("expected %v result(s), got %v", len(tt.expBlockFields), len(resp.Results)) + } + + for i, exp := range tt.expBlockFields { + if exp.id != resp.Results[i].ID() { + t.Fatalf("id not equal (expected: %v, got: %v)", exp.id, resp.Results[i].ID()) + } + + if exp.createdTime != resp.Results[i].CreatedTime() { + t.Fatalf("createdTime not equal (expected: %v, got: %v)", exp.createdTime, resp.Results[i].CreatedTime()) + } + + if exp.lastEditedTime != resp.Results[i].LastEditedTime() { + t.Fatalf("lastEditedTime not equal (expected: %v, got: %v)", exp.lastEditedTime, resp.Results[i].LastEditedTime()) + } + + if exp.hasChildren != resp.Results[i].HasChildren() { + t.Fatalf("hasChildren not equal (expected: %v, got: %v)", exp.hasChildren, resp.Results[i].HasChildren()) + } + + if exp.archived != resp.Results[i].Archived() { + t.Fatalf("archived not equal (expected: %v, got: %v)", exp.archived, resp.Results[i].Archived()) + } + } }) } } @@ -3939,12 +3992,17 @@ func TestFindBlockByID(t *testing.T) { t.Parallel() tests := []struct { - name string - blockID string - respBody func(r *http.Request) io.Reader - respStatusCode int - expBlock notion.Block - expError error + name string + blockID string + respBody func(r *http.Request) io.Reader + respStatusCode int + expBlock notion.Block + expID string + expCreatedTime time.Time + expLastEditedTime time.Time + expHasChildren bool + expArchived bool + expError error }{ { name: "successful response", @@ -3966,17 +4024,15 @@ func TestFindBlockByID(t *testing.T) { ) }, respStatusCode: http.StatusOK, - expBlock: notion.Block{ - Object: "block", - ID: "048e165e-352d-4119-8128-e46c3527d95c", - Type: "child_page", - CreatedTime: mustParseTimePointer(time.RFC3339, "2021-10-02T06:09:00Z"), - LastEditedTime: mustParseTimePointer(time.RFC3339, "2021-10-02T06:31:00Z"), - HasChildren: true, - ChildPage: ¬ion.ChildPage{Title: "test title"}, - Archived: notion.BoolPtr(false), + expBlock: ¬ion.ChildPageBlock{ + Title: "test title", }, - expError: nil, + expID: "048e165e-352d-4119-8128-e46c3527d95c", + expCreatedTime: mustParseTime(time.RFC3339, "2021-10-02T06:09:00Z"), + expLastEditedTime: mustParseTime(time.RFC3339, "2021-10-02T06:31:00Z"), + expHasChildren: true, + expArchived: false, + expError: nil, }, { name: "error response not found", @@ -3991,7 +4047,7 @@ func TestFindBlockByID(t *testing.T) { ) }, respStatusCode: http.StatusNotFound, - expBlock: notion.Block{}, + expBlock: nil, expError: errors.New("notion: failed to find block: Could not find block with ID: test id. (code: object_not_found, status: 404)"), }, } @@ -4023,9 +4079,31 @@ func TestFindBlockByID(t *testing.T) { t.Fatalf("error not equal (expected: %v, got: %v)", tt.expError, err) } - if diff := cmp.Diff(tt.expBlock, block); diff != "" { + if diff := cmp.Diff(tt.expBlock, block, cmpopts.IgnoreUnexported(notion.ChildPageBlock{})); diff != "" { t.Fatalf("user not equal (-exp, +got):\n%v", diff) } + + if block != nil { + if tt.expID != block.ID() { + t.Fatalf("id not equal (expected: %v, got: %v)", tt.expID, block.ID()) + } + + if tt.expCreatedTime != block.CreatedTime() { + t.Fatalf("createdTime not equal (expected: %v, got: %v)", tt.expCreatedTime, block.CreatedTime()) + } + + if tt.expLastEditedTime != block.LastEditedTime() { + t.Fatalf("lastEditedTime not equal (expected: %v, got: %v)", tt.expLastEditedTime, block.LastEditedTime()) + } + + if tt.expHasChildren != block.HasChildren() { + t.Fatalf("hasChildren not equal (expected: %v, got: %v)", tt.expHasChildren, block.HasChildren()) + } + + if tt.expArchived != block.Archived() { + t.Fatalf("archived not equal (expected: %v, got: %v)", tt.expArchived, block.Archived()) + } + } }) } } @@ -4034,23 +4112,26 @@ func TestUpdateBlock(t *testing.T) { t.Parallel() tests := []struct { - name string - block notion.Block - respBody func(r *http.Request) io.Reader - respStatusCode int - expPostBody map[string]interface{} - expResponse notion.Block - expError error + name string + block notion.Block + respBody func(r *http.Request) io.Reader + respStatusCode int + expPostBody map[string]interface{} + expResponse notion.Block + expID string + expCreatedTime time.Time + expLastEditedTime time.Time + expHasChildren bool + expArchived bool + expError error }{ { name: "successful response", - block: notion.Block{ - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Text: ¬ion.Text{ - Content: "Foobar", - }, + block: ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Text: ¬ion.Text{ + Content: "Foobar", }, }, }, @@ -4091,7 +4172,6 @@ func TestUpdateBlock(t *testing.T) { }, respStatusCode: http.StatusOK, expPostBody: map[string]interface{}{ - "object": "block", "paragraph": map[string]interface{}{ "text": []interface{}{ map[string]interface{}{ @@ -4102,40 +4182,34 @@ func TestUpdateBlock(t *testing.T) { }, }, }, - expResponse: notion.Block{ - Object: "block", - ID: "048e165e-352d-4119-8128-e46c3527d95c", - Type: notion.BlockTypeParagraph, - CreatedTime: mustParseTimePointer(time.RFC3339, "2021-10-02T06:09:00Z"), - LastEditedTime: mustParseTimePointer(time.RFC3339, "2021-10-02T06:31:00Z"), - HasChildren: true, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Type: notion.RichTextTypeText, - Text: ¬ion.Text{ - Content: "Foobar", - }, - PlainText: "Foobar", - Annotations: ¬ion.Annotations{ - Color: notion.ColorDefault, - }, + expResponse: ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Type: notion.RichTextTypeText, + Text: ¬ion.Text{ + Content: "Foobar", + }, + PlainText: "Foobar", + Annotations: ¬ion.Annotations{ + Color: notion.ColorDefault, }, }, }, - Archived: notion.BoolPtr(false), }, - expError: nil, + expID: "048e165e-352d-4119-8128-e46c3527d95c", + expCreatedTime: mustParseTime(time.RFC3339, "2021-10-02T06:09:00Z"), + expLastEditedTime: mustParseTime(time.RFC3339, "2021-10-02T06:31:00Z"), + expHasChildren: true, + expArchived: false, + expError: nil, }, { name: "error response", - block: notion.Block{ - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Text: ¬ion.Text{ - Content: "Foobar", - }, + block: ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Text: ¬ion.Text{ + Content: "Foobar", }, }, }, @@ -4152,7 +4226,6 @@ func TestUpdateBlock(t *testing.T) { }, respStatusCode: http.StatusBadRequest, expPostBody: map[string]interface{}{ - "object": "block", "paragraph": map[string]interface{}{ "text": []interface{}{ map[string]interface{}{ @@ -4163,7 +4236,7 @@ func TestUpdateBlock(t *testing.T) { }, }, }, - expResponse: notion.Block{}, + expResponse: nil, expError: errors.New("notion: failed to update block: foobar (code: validation_error, status: 400)"), }, } @@ -4216,9 +4289,31 @@ func TestUpdateBlock(t *testing.T) { t.Fatalf("error not equal (expected: %v, got: %v)", tt.expError, err) } - if diff := cmp.Diff(tt.expResponse, updatedBlock); diff != "" { + if diff := cmp.Diff(tt.expResponse, updatedBlock, cmpopts.IgnoreUnexported(notion.ParagraphBlock{})); diff != "" { t.Fatalf("response not equal (-exp, +got):\n%v", diff) } + + if updatedBlock != nil { + if tt.expID != updatedBlock.ID() { + t.Fatalf("id not equal (expected: %v, got: %v)", tt.expID, updatedBlock.ID()) + } + + if tt.expCreatedTime != updatedBlock.CreatedTime() { + t.Fatalf("createdTime not equal (expected: %v, got: %v)", tt.expCreatedTime, updatedBlock.CreatedTime()) + } + + if tt.expLastEditedTime != updatedBlock.LastEditedTime() { + t.Fatalf("lastEditedTime not equal (expected: %v, got: %v)", tt.expLastEditedTime, updatedBlock.LastEditedTime()) + } + + if tt.expHasChildren != updatedBlock.HasChildren() { + t.Fatalf("hasChildren not equal (expected: %v, got: %v)", tt.expHasChildren, updatedBlock.HasChildren()) + } + + if tt.expArchived != updatedBlock.Archived() { + t.Fatalf("archived not equal (expected: %v, got: %v)", tt.expArchived, updatedBlock.Archived()) + } + } }) } } @@ -4227,11 +4322,16 @@ func TestDeleteBlock(t *testing.T) { t.Parallel() tests := []struct { - name string - respBody func(r *http.Request) io.Reader - respStatusCode int - expResponse notion.Block - expError error + name string + respBody func(r *http.Request) io.Reader + respStatusCode int + expResponse notion.Block + expID string + expCreatedTime time.Time + expLastEditedTime time.Time + expHasChildren bool + expArchived bool + expError error }{ { name: "successful response", @@ -4270,30 +4370,26 @@ func TestDeleteBlock(t *testing.T) { ) }, respStatusCode: http.StatusOK, - expResponse: notion.Block{ - Object: "block", - ID: "048e165e-352d-4119-8128-e46c3527d95c", - Type: notion.BlockTypeParagraph, - CreatedTime: mustParseTimePointer(time.RFC3339, "2021-10-02T06:09:00Z"), - LastEditedTime: mustParseTimePointer(time.RFC3339, "2021-10-02T06:31:00Z"), - HasChildren: true, - Paragraph: ¬ion.RichTextBlock{ - Text: []notion.RichText{ - { - Type: notion.RichTextTypeText, - Text: ¬ion.Text{ - Content: "Foobar", - }, - PlainText: "Foobar", - Annotations: ¬ion.Annotations{ - Color: notion.ColorDefault, - }, + expResponse: ¬ion.ParagraphBlock{ + Text: []notion.RichText{ + { + Type: notion.RichTextTypeText, + Text: ¬ion.Text{ + Content: "Foobar", + }, + PlainText: "Foobar", + Annotations: ¬ion.Annotations{ + Color: notion.ColorDefault, }, }, }, - Archived: notion.BoolPtr(true), }, - expError: nil, + expID: "048e165e-352d-4119-8128-e46c3527d95c", + expCreatedTime: mustParseTime(time.RFC3339, "2021-10-02T06:09:00Z"), + expLastEditedTime: mustParseTime(time.RFC3339, "2021-10-02T06:31:00Z"), + expHasChildren: true, + expArchived: true, + expError: nil, }, { name: "error response", @@ -4308,7 +4404,7 @@ func TestDeleteBlock(t *testing.T) { ) }, respStatusCode: http.StatusBadRequest, - expResponse: notion.Block{}, + expResponse: nil, expError: errors.New("notion: failed to delete block: foobar (code: validation_error, status: 400)"), }, } @@ -4340,9 +4436,31 @@ func TestDeleteBlock(t *testing.T) { t.Fatalf("error not equal (expected: %v, got: %v)", tt.expError, err) } - if diff := cmp.Diff(tt.expResponse, deletedBlock); diff != "" { + if diff := cmp.Diff(tt.expResponse, deletedBlock, cmpopts.IgnoreUnexported(notion.ParagraphBlock{})); diff != "" { t.Fatalf("response not equal (-exp, +got):\n%v", diff) } + + if deletedBlock != nil { + if tt.expID != deletedBlock.ID() { + t.Fatalf("id not equal (expected: %v, got: %v)", tt.expID, deletedBlock.ID()) + } + + if tt.expCreatedTime != deletedBlock.CreatedTime() { + t.Fatalf("createdTime not equal (expected: %v, got: %v)", tt.expCreatedTime, deletedBlock.CreatedTime()) + } + + if tt.expLastEditedTime != deletedBlock.LastEditedTime() { + t.Fatalf("lastEditedTime not equal (expected: %v, got: %v)", tt.expLastEditedTime, deletedBlock.LastEditedTime()) + } + + if tt.expHasChildren != deletedBlock.HasChildren() { + t.Fatalf("hasChildren not equal (expected: %v, got: %v)", tt.expHasChildren, deletedBlock.HasChildren()) + } + + if tt.expArchived != deletedBlock.Archived() { + t.Fatalf("archived not equal (expected: %v, got: %v)", tt.expArchived, deletedBlock.Archived()) + } + } }) } }