From 7ea8c5afded4c42f45bd6a29f67f2927bc678bca Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Sun, 16 May 2021 12:22:25 +0800 Subject: [PATCH 01/32] ci: add develop workflow (#1) --- .github/workflows/develop.yaml | 36 ++++++++++++++++++++++++++++++++++ .golangci.yml | 24 +++++++++++++++++++++++ api.go | 6 ++++++ blocks.go | 4 ++++ databases.go | 4 +++- pages.go | 4 ++++ search.go | 3 +++ 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/develop.yaml create mode 100644 .golangci.yml diff --git a/.github/workflows/develop.yaml b/.github/workflows/develop.yaml new file mode 100644 index 0000000..ac3f8ec --- /dev/null +++ b/.github/workflows/develop.yaml @@ -0,0 +1,36 @@ +name: Lint, Test and Build +on: + push: + branches: + - develop + pull_request: +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.28.3 + + build: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: + - 1.16.x + steps: + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go-version }} + + - name: Check out code + uses: actions/checkout@v1 + + - name: Tesing + run: go test -v ./... + + - name: Build binary + run: go build -v diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d5741f6 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,24 @@ +# https://github.com/golangci/golangci-lint#config-file +run: + skip-dirs: + - pkg/orm + - mock_* + # build-tags: + tests: false + deadline: 5m + print-resources-usage: true + +linters: + enable-all: true + disable: + - godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] + - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false] + - gofumpt + +linters-settings: + govet: + # https://github.com/golangci/golangci-lint/issues/484 + # report about shadowed variables + check-shadowing: false + lll: + line-length: 150 diff --git a/api.go b/api.go index 30c7b55..402b6a9 100644 --- a/api.go +++ b/api.go @@ -33,6 +33,7 @@ type API struct { func New(setters ...ClientOption) *API { options := defaultOptions + for _, setter := range setters { setter(&options) } @@ -52,6 +53,7 @@ func (c *API) Users() UsersInterface { if c == nil { return nil } + return c.usersClient } @@ -59,6 +61,7 @@ func (c *API) Databases() DatabasesInterface { if c == nil { return nil } + return c.databasesClient } @@ -66,6 +69,7 @@ func (c *API) Pages() PagesInterface { if c == nil { return nil } + return c.pagesClient } @@ -73,6 +77,7 @@ func (c *API) Blocks() BlocksInterface { if c == nil { return nil } + return c.blocksClient } @@ -80,5 +85,6 @@ func (c *API) Search(ctx context.Context, params SearchParameters) (*SearchRespo if c == nil { return nil, ErrUnknown } + return c.searchClient.Search(ctx, params) } diff --git a/blocks.go b/blocks.go index 82ae274..7de9416 100644 --- a/blocks.go +++ b/blocks.go @@ -12,6 +12,8 @@ type Block interface { isBlock() } +// FIXME: reduce function length +// nolint:funlen func newBlock(data []byte) (Block, error) { var base BlockBase @@ -232,6 +234,7 @@ func (b *blocksClient) Children() BlocksChildrenInterface { if b == nil { return nil } + return b.childrenClient } @@ -268,6 +271,7 @@ func (b *BlocksChildrenListResponse) UnmarshalJSON(data []byte) error { if err != nil { return err } + b.Results = append(b.Results, block) } diff --git a/databases.go b/databases.go index 91bb4a0..9752015 100644 --- a/databases.go +++ b/databases.go @@ -20,6 +20,8 @@ type Database struct { func (d Database) isSearchable() {} +// FIXME: reduce the complexity +// nolint:gocyclo,gocognit,funlen func (d *Database) UnmarshalJSON(data []byte) error { type Alias Database @@ -680,7 +682,7 @@ type NumberFilter struct { IsNotEmpty *bool `json:"is_not_empty,omitempty"` } -// SingleNumberFilter is a number filter condition applies to database properties of type "number" +// SingleNumberFilter is a number filter condition applies to database properties of type "number". type SingleNumberFilter struct { SinglePropertyFilter Number NumberFilter `json:"number"` diff --git a/pages.go b/pages.go index 534d498..3ecc784 100644 --- a/pages.go +++ b/pages.go @@ -75,6 +75,8 @@ type Page struct { Archived bool `json:"archived"` } +// FIXME: reduce the complexity +// nolint:gocyclo,gocognit,funlen func (p *Page) UnmarshalJSON(data []byte) error { type Alias Page @@ -368,6 +370,7 @@ func (t *TitlePropertyValue) UnmarshalJSON(data []byte) error { if err != nil { return err } + t.Title = append(t.Title, richText) } @@ -400,6 +403,7 @@ func (r *RichTextPropertyValue) UnmarshalJSON(data []byte) error { if err != nil { return err } + r.RichText = append(r.RichText, richText) } diff --git a/search.go b/search.go index 2a914b6..9dabd1e 100644 --- a/search.go +++ b/search.go @@ -109,6 +109,9 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { } s.Results = append(s.Results, object) + + case ObjectTypeBlock: + continue } } From f91053425360479f6d7931d5133f5dabaa5b1aa3 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Sun, 16 May 2021 14:54:29 +0800 Subject: [PATCH 02/32] refactor: reuse newUser function (#2) * refactor: reuse newUser function * ci: fix Fetching the repository --- .github/workflows/develop.yaml | 5 +- users.go | 86 ++++++++++++++-------------------- 2 files changed, 38 insertions(+), 53 deletions(-) diff --git a/.github/workflows/develop.yaml b/.github/workflows/develop.yaml index ac3f8ec..ba6256e 100644 --- a/.github/workflows/develop.yaml +++ b/.github/workflows/develop.yaml @@ -8,7 +8,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2.3.3 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: @@ -26,8 +26,7 @@ jobs: with: go-version: ${{ matrix.go-version }} - - name: Check out code - uses: actions/checkout@v1 + - uses: actions/checkout@v2.3.3 - name: Tesing run: go test -v ./... diff --git a/users.go b/users.go index 811689d..852a599 100644 --- a/users.go +++ b/users.go @@ -18,6 +18,36 @@ type User interface { isUser() } +func newUser(data []byte) (User, error) { + var base baseUser + + if err := json.Unmarshal(data, &base); err != nil { + return nil, err + } + + switch base.Type { + case UserTypePerson: + var user PersonUser + + if err := json.Unmarshal(data, &user); err != nil { + return nil, err + } + + return user, nil + + case UserTypeBot: + var user BotUser + + if err := json.Unmarshal(data, &user); err != nil { + return nil, err + } + + return user, nil + } + + return nil, ErrUnknown +} + type baseUser struct { Object string `json:"object"` ID string `json:"id"` @@ -48,34 +78,9 @@ type UsersRetrieveResponse struct { User } -func (u *UsersRetrieveResponse) UnmarshalJSON(data []byte) error { - var base baseUser - - if err := json.Unmarshal(data, &base); err != nil { - return err - } - - switch base.Type { - case UserTypePerson: - var user PersonUser - - if err := json.Unmarshal(data, &user); err != nil { - return err - } - - u.User = user - - case UserTypeBot: - var user BotUser - - if err := json.Unmarshal(data, &user); err != nil { - return err - } - - u.User = user - } - - return nil +func (u *UsersRetrieveResponse) UnmarshalJSON(data []byte) (err error) { + u.User, err = newUser(data) + return } type UsersListParameters struct { @@ -104,31 +109,12 @@ func (u *UsersListResponse) UnmarshalJSON(data []byte) error { u.Results = make([]User, 0, len(alias.Results)) for _, result := range alias.Results { - var base baseUser - - if err := json.Unmarshal(result, &base); err != nil { + user, err := newUser(result) + if err != nil { return err } - switch base.Type { - case UserTypePerson: - var user PersonUser - - if err := json.Unmarshal(result, &user); err != nil { - return err - } - - u.Results = append(u.Results, user) - - case UserTypeBot: - var user BotUser - - if err := json.Unmarshal(result, &user); err != nil { - return err - } - - u.Results = append(u.Results, user) - } + u.Results = append(u.Results, user) } return nil From b1fd52891110ac684bfb4fa0079b598dd508094b Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Sun, 16 May 2021 15:28:30 +0800 Subject: [PATCH 03/32] refactor: move string types to typed package --- README.md | 6 ++ blocks.go | 41 ++++------ databases.go | 195 +++++++++++++++------------------------------- pages.go | 112 +++++++++----------------- search.go | 42 +++------- typed/block.go | 16 ++++ typed/color.go | 25 ++++++ typed/format.go | 17 ++++ typed/formula.go | 10 +++ typed/function.go | 19 +++++ typed/object.go | 10 +++ typed/parent.go | 9 +++ typed/property.go | 48 ++++++++++++ typed/richtext.go | 9 +++ typed/search.go | 27 +++++++ typed/sort.go | 15 ++++ typed/user.go | 8 ++ types.go | 38 ++------- users.go | 21 ++--- 19 files changed, 354 insertions(+), 314 deletions(-) create mode 100644 typed/block.go create mode 100644 typed/color.go create mode 100644 typed/format.go create mode 100644 typed/formula.go create mode 100644 typed/function.go create mode 100644 typed/object.go create mode 100644 typed/parent.go create mode 100644 typed/property.go create mode 100644 typed/richtext.go create mode 100644 typed/search.go create mode 100644 typed/sort.go create mode 100644 typed/user.go diff --git a/README.md b/README.md index 374e260..80c5fab 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ which is written in JavaScript. go get -u github.com/mkfsn/notion-go ``` +## Usage + +```go + +``` + ## Supported Features This client supports all endpoints in the [Notion API](https://developers.notion.com/reference/intro). diff --git a/blocks.go b/blocks.go index 7de9416..9d7cd3e 100644 --- a/blocks.go +++ b/blocks.go @@ -6,6 +6,8 @@ import ( "net/http" "strings" "time" + + "github.com/mkfsn/notion-go/typed" ) type Block interface { @@ -22,7 +24,7 @@ func newBlock(data []byte) (Block, error) { } switch base.Type { - case BlockTypeParagraph: + case typed.BlockTypeParagraph: var block ParagraphBlock if err := json.Unmarshal(data, &block); err != nil { @@ -31,7 +33,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeHeading1: + case typed.BlockTypeHeading1: var block Heading1Block if err := json.Unmarshal(data, &block); err != nil { @@ -40,7 +42,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeHeading2: + case typed.BlockTypeHeading2: var block Heading2Block if err := json.Unmarshal(data, &block); err != nil { @@ -49,7 +51,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeHeading3: + case typed.BlockTypeHeading3: var block Heading3Block if err := json.Unmarshal(data, &block); err != nil { @@ -58,7 +60,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeBulletedListItem: + case typed.BlockTypeBulletedListItem: var block BulletedListItemBlock if err := json.Unmarshal(data, &block); err != nil { @@ -67,7 +69,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeNumberedListItem: + case typed.BlockTypeNumberedListItem: var block NumberedListItemBlock if err := json.Unmarshal(data, &block); err != nil { @@ -76,7 +78,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeToDo: + case typed.BlockTypeToDo: var block ToDoBlock if err := json.Unmarshal(data, &block); err != nil { @@ -85,7 +87,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeToggle: + case typed.BlockTypeToggle: var block ToggleBlock if err := json.Unmarshal(data, &block); err != nil { @@ -94,7 +96,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeChildPage: + case typed.BlockTypeChildPage: var block ChildPageBlock if err := json.Unmarshal(data, &block); err != nil { @@ -103,7 +105,7 @@ func newBlock(data []byte) (Block, error) { return block, nil - case BlockTypeUnsupported: + case typed.BlockTypeUnsupported: var block UnsupportedBlock if err := json.Unmarshal(data, &block); err != nil { @@ -116,28 +118,13 @@ func newBlock(data []byte) (Block, error) { return nil, ErrUnknown } -type BlockType string - -const ( - BlockTypeParagraph BlockType = "paragraph" - BlockTypeHeading1 BlockType = "heading_1" - BlockTypeHeading2 BlockType = "heading_2" - BlockTypeHeading3 BlockType = "heading_3" - BlockTypeBulletedListItem BlockType = "bulleted_list_item" - BlockTypeNumberedListItem BlockType = "numbered_list_item" - BlockTypeToDo BlockType = "to_do" - BlockTypeToggle BlockType = "toggle" - BlockTypeChildPage BlockType = "child_page" - BlockTypeUnsupported BlockType = "unsupported" -) - type BlockBase struct { // Always "block". - Object ObjectType `json:"object"` + Object typed.ObjectType `json:"object"` // Identifier for the block. ID string `json:"id,omitempty"` // Type of block. - Type BlockType `json:"type"` + Type typed.BlockType `json:"type"` // Date and time when this block was created. Formatted as an ISO 8601 date time string. CreatedTime *time.Time `json:"created_time,omitempty"` // Date and time when this block was last updated. Formatted as an ISO 8601 date time string. diff --git a/databases.go b/databases.go index 9752015..0208bae 100644 --- a/databases.go +++ b/databases.go @@ -6,11 +6,13 @@ import ( "net/http" "strings" "time" + + "github.com/mkfsn/notion-go/typed" ) type Database struct { - Object ObjectType `json:"object"` - ID string `json:"id"` + Object typed.ObjectType `json:"object"` + ID string `json:"id"` CreatedTime time.Time `json:"created_time"` LastEditedTime time.Time `json:"last_edited_time"` @@ -48,7 +50,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { } switch base.Type { - case RichTextTypeText: + case typed.RichTextTypeText: var richText RichTextText if err := json.Unmarshal(title, &richText); err != nil { @@ -57,7 +59,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Title = append(d.Title, richText) - case RichTextTypeMention: + case typed.RichTextTypeMention: var richText RichTextMention if err := json.Unmarshal(title, &richText); err != nil { @@ -66,7 +68,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Title = append(d.Title, richText) - case RichTextTypeEquation: + case typed.RichTextTypeEquation: var richText RichTextEquation if err := json.Unmarshal(title, &richText); err != nil { @@ -85,7 +87,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { } switch base.Type { - case PropertyTypeTitle: + case typed.PropertyTypeTitle: var property TitleProperty if err := json.Unmarshal(value, &property); err != nil { @@ -94,7 +96,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeRichText: + case typed.PropertyTypeRichText: var property RichTextProperty if err := json.Unmarshal(value, &property); err != nil { @@ -103,7 +105,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeNumber: + case typed.PropertyTypeNumber: var property NumberProperty if err := json.Unmarshal(value, &property); err != nil { @@ -112,7 +114,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeSelect: + case typed.PropertyTypeSelect: var property SelectProperty if err := json.Unmarshal(value, &property); err != nil { @@ -121,7 +123,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeMultiSelect: + case typed.PropertyTypeMultiSelect: var property MultiSelectProperty if err := json.Unmarshal(value, &property); err != nil { @@ -130,7 +132,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeDate: + case typed.PropertyTypeDate: var property DateProperty if err := json.Unmarshal(value, &property); err != nil { @@ -139,7 +141,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypePeople: + case typed.PropertyTypePeople: var property PeopleProperty if err := json.Unmarshal(value, &property); err != nil { @@ -148,7 +150,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeFile: + case typed.PropertyTypeFile: var property FileProperty if err := json.Unmarshal(value, &property); err != nil { @@ -157,7 +159,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeCheckbox: + case typed.PropertyTypeCheckbox: var property CheckboxProperty if err := json.Unmarshal(value, &property); err != nil { @@ -166,7 +168,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeURL: + case typed.PropertyTypeURL: var property URLProperty if err := json.Unmarshal(value, &property); err != nil { @@ -175,7 +177,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeEmail: + case typed.PropertyTypeEmail: var property EmailProperty if err := json.Unmarshal(value, &property); err != nil { @@ -184,7 +186,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypePhoneNumber: + case typed.PropertyTypePhoneNumber: var property PhoneNumberProperty if err := json.Unmarshal(value, &property); err != nil { @@ -193,7 +195,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeFormula: + case typed.PropertyTypeFormula: var property FormulaProperty if err := json.Unmarshal(value, &property); err != nil { @@ -202,7 +204,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeRelation: + case typed.PropertyTypeRelation: var property RelationProperty if err := json.Unmarshal(value, &property); err != nil { @@ -211,7 +213,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeRollup: + case typed.PropertyTypeRollup: var property RollupProperty if err := json.Unmarshal(value, &property); err != nil { @@ -220,7 +222,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeCreatedTime: + case typed.PropertyTypeCreatedTime: var property CreatedTimeProperty if err := json.Unmarshal(value, &property); err != nil { @@ -229,7 +231,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeCreatedBy: + case typed.PropertyTypeCreatedBy: var property CreatedByProperty if err := json.Unmarshal(value, &property); err != nil { @@ -238,7 +240,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeLastEditedTime: + case typed.PropertyTypeLastEditedTime: var property LastEditedTimeProperty if err := json.Unmarshal(value, &property); err != nil { @@ -247,7 +249,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { d.Properties[name] = property - case PropertyTypeLastEditedBy: + case typed.PropertyTypeLastEditedBy: var property LastEditedByProperty if err := json.Unmarshal(value, &property); err != nil { @@ -273,7 +275,7 @@ type Annotations struct { // Whether the text is `code style`. Code bool `json:"code"` // Color of the text. - Color Color `json:"color"` + Color typed.Color `json:"color"` } type RichText interface { @@ -288,7 +290,7 @@ func newRichText(data []byte) (RichText, error) { } switch base.Type { - case RichTextTypeText: + case typed.RichTextTypeText: var richText RichTextText if err := json.Unmarshal(data, &richText); err != nil { @@ -297,7 +299,7 @@ func newRichText(data []byte) (RichText, error) { return richText, nil - case RichTextTypeMention: + case typed.RichTextTypeMention: var richText RichTextMention if err := json.Unmarshal(data, &richText); err != nil { @@ -306,7 +308,7 @@ func newRichText(data []byte) (RichText, error) { return richText, nil - case RichTextTypeEquation: + case typed.RichTextTypeEquation: var richText RichTextEquation if err := json.Unmarshal(data, &richText); err != nil { @@ -319,21 +321,13 @@ func newRichText(data []byte) (RichText, error) { return nil, ErrUnknown } -type RichTextType string - -const ( - RichTextTypeText RichTextType = "text" - RichTextTypeMention RichTextType = "mention" - RichTextTypeEquation RichTextType = "equation" -) - type BaseRichText struct { // The plain text without annotations. PlainText string `json:"plain_text,omitempty"` // (Optional) The URL of any link or internal Notion mention in this text, if any. Href string `json:"href,omitempty"` // Type of this rich text object. - Type RichTextType `json:"type"` + Type typed.RichTextType `json:"type"` // All annotations that apply to this rich text. // Annotations include colors and bold/italics/underline/strikethrough. Annotations *Annotations `json:"annotations,omitempty"` @@ -408,37 +402,13 @@ type Property interface { isProperty() } -type PropertyType string - -const ( - PropertyTypeTitle PropertyType = "title" - PropertyTypeRichText PropertyType = "rich_text" - PropertyTypeNumber PropertyType = "number" - PropertyTypeSelect PropertyType = "select" - PropertyTypeMultiSelect PropertyType = "multi_select" - PropertyTypeDate PropertyType = "date" - PropertyTypePeople PropertyType = "people" - PropertyTypeFile PropertyType = "file" - PropertyTypeCheckbox PropertyType = "checkbox" - PropertyTypeURL PropertyType = "url" - PropertyTypeEmail PropertyType = "email" - PropertyTypePhoneNumber PropertyType = "phone_number" - PropertyTypeFormula PropertyType = "formula" - PropertyTypeRelation PropertyType = "relation" - PropertyTypeRollup PropertyType = "rollup" - PropertyTypeCreatedTime PropertyType = "created_time" - PropertyTypeCreatedBy PropertyType = "created_by" - PropertyTypeLastEditedTime PropertyType = "last_edited_time" - PropertyTypeLastEditedBy PropertyType = "last_edited_by" -) - type baseProperty struct { // The ID of the property, usually a short string of random letters and symbols. // Some automatically generated property types have special human-readable IDs. // For example, all Title properties have an ID of "title". ID string `json:"id"` // Type that controls the behavior of the property - Type PropertyType `json:"type"` + Type typed.PropertyType `json:"type"` } func (p baseProperty) isProperty() {} @@ -453,54 +423,43 @@ type RichTextProperty struct { RichText interface{} `json:"rich_text"` } -type NumberFormat string - -const ( - NumberFormatNumber NumberFormat = "number" - NumberFormatNumberWithCommas NumberFormat = "number_with_commas" - NumberFormatPercent NumberFormat = "percent" - NumberFormatDollar NumberFormat = "dollar" - NumberFormatEuro NumberFormat = "euro" - NumberFormatPound NumberFormat = "pound" - NumberFormatYen NumberFormat = "yen" - NumberFormatRuble NumberFormat = "ruble" - NumberFormatRupee NumberFormat = "rupee" - NumberFormatWon NumberFormat = "won" - NumberFormatYuan NumberFormat = "yuan" -) +type NumberPropertyOption struct { + Format typed.NumberFormat `json:"format"` +} type NumberProperty struct { baseProperty - Number struct { - Format NumberFormat `json:"format"` - } `json:"number"` + Number NumberPropertyOption `json:"number"` } type SelectOption struct { - Name string `json:"name"` - ID string `json:"id"` - Color Color `json:"color"` + Name string `json:"name"` + ID string `json:"id"` + Color typed.Color `json:"color"` } type MultiSelectOption struct { - Name string `json:"name"` - ID string `json:"id"` - Color Color `json:"color"` + Name string `json:"name"` + ID string `json:"id"` + Color typed.Color `json:"color"` +} + +type SelectPropertyOption struct { + Options []SelectOption `json:"options"` } type SelectProperty struct { baseProperty + Select SelectPropertyOption `json:"select"` +} - Select struct { - Options []SelectOption `json:"options"` - } `json:"select"` +type MultiSelectPropertyOption struct { + Options []MultiSelectOption `json:"options"` } type MultiSelectProperty struct { baseProperty - MultiSelect struct { - Options []MultiSelectOption `json:"options"` - } `json:"multi_select"` + MultiSelect MultiSelectPropertyOption `json:"multi_select"` } type DateProperty struct { @@ -554,33 +513,17 @@ type RelationProperty struct { } `json:"relation"` } -type RollupFunction string - -const ( - RollupFunctionCountAll RollupFunction = "count_all" - RollupFunctionCountValues RollupFunction = "count_values" - RollupFunctionCountUniqueValues RollupFunction = "count_unique_values" - RollupFunctionCountEmpty RollupFunction = "count_empty" - RollupFunctionCountNotEmpty RollupFunction = "count_not_empty" - RollupFunctionPercentEmpty RollupFunction = "percent_empty" - RollupFunctionPercentNotEmpty RollupFunction = "percent_not_empty" - RollupFunctionSum RollupFunction = "sum" - RollupFunctionAverage RollupFunction = "average" - RollupFunctionMedian RollupFunction = "median" - RollupFunctionMin RollupFunction = "min" - RollupFunctionMax RollupFunction = "max" - RollupFunctionRange RollupFunction = "range" -) +type RollupPropertyOption struct { + RelationPropertyName string `json:"relation_property_name"` + RelationPropertyID string `json:"relation_property_id"` + RollupPropertyName string `json:"rollup_property_name"` + RollupPropertyID string `json:"rollup_property_id"` + Function typed.RollupFunction `json:"function"` +} type RollupProperty struct { baseProperty - Rollup struct { - RelationPropertyName string `json:"relation_property_name"` - RelationPropertyID string `json:"relation_property_id"` - RollupPropertyName string `json:"rollup_property_name"` - RollupPropertyID string `json:"rollup_property_id"` - Function RollupFunction `json:"function"` - } `json:"rollup"` + Rollup RollupPropertyOption `json:"rollup"` } type CreatedTimeProperty struct { @@ -620,24 +563,10 @@ type DatabasesListResponse struct { Results []Database `json:"results"` } -type SortTimestamp string - -const ( - SortTimestampByCreatedTime SortTimestamp = "created_time" - SortTimestampByLastEditedTime SortTimestamp = "last_edited_time" -) - -type SortDirection string - -const ( - SortDirectionAscending SortDirection = "ascending" - SortDirectionDescending SortDirection = "descending" -) - type Sort struct { - Property string `json:"property,omitempty"` - Timestamp SortTimestamp `json:"timestamp,omitempty"` - Direction SortDirection `json:"direction,omitempty"` + Property string `json:"property,omitempty"` + Timestamp typed.SortTimestamp `json:"timestamp,omitempty"` + Direction typed.SortDirection `json:"direction,omitempty"` } type Filter interface { diff --git a/pages.go b/pages.go index 3ecc784..4e6082f 100644 --- a/pages.go +++ b/pages.go @@ -6,22 +6,16 @@ import ( "net/http" "strings" "time" + + "github.com/mkfsn/notion-go/typed" ) type Parent interface { isParent() } -type ParentType string - -const ( - ParentTypeDatabase ParentType = "database_id" - ParentTypePage ParentType = "page" - ParentTypeWorkspace ParentType = "workspace" -) - type baseParent struct { - Type ParentType `json:"type"` + Type typed.ParentType `json:"type"` } func (b baseParent) isParent() {} @@ -60,7 +54,7 @@ type PageParentInput struct { type Page struct { // Always "page". - Object ObjectType `json:"object"` + Object typed.ObjectType `json:"object"` // Unique identifier of the page. ID string `json:"id"` // The page's parent @@ -99,7 +93,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { } switch baseParent.Type { - case ParentTypeDatabase: + case typed.ParentTypeDatabase: var parent DatabaseParent if err := json.Unmarshal(alias.Parent, &parent); err != nil { @@ -108,7 +102,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Parent = parent - case ParentTypePage: + case typed.ParentTypePage: var parent PageParent if err := json.Unmarshal(alias.Parent, &parent); err != nil { @@ -117,7 +111,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Parent = parent - case ParentTypeWorkspace: + case typed.ParentTypeWorkspace: var parent WorkspaceParent if err := json.Unmarshal(alias.Parent, &parent); err != nil { @@ -137,7 +131,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { } switch base.Type { - case PropertyValueTypeRichText: + case typed.PropertyValueTypeRichText: var property RichTextPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -146,7 +140,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeNumber: + case typed.PropertyValueTypeNumber: var property NumberPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -155,7 +149,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeSelect: + case typed.PropertyValueTypeSelect: var property SelectPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -164,7 +158,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeMultiSelect: + case typed.PropertyValueTypeMultiSelect: var property MultiSelectPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -173,7 +167,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeDate: + case typed.PropertyValueTypeDate: var property DatePropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -182,7 +176,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeFormula: + case typed.PropertyValueTypeFormula: var property FormulaPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -191,7 +185,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeRollup: + case typed.PropertyValueTypeRollup: var property RollupPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -200,7 +194,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeTitle: + case typed.PropertyValueTypeTitle: var property TitlePropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -209,7 +203,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypePeople: + case typed.PropertyValueTypePeople: var property PeoplePropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -218,7 +212,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeFiles: + case typed.PropertyValueTypeFiles: var property FilesPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -227,7 +221,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeCheckbox: + case typed.PropertyValueTypeCheckbox: var property CheckboxPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -236,7 +230,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeURL: + case typed.PropertyValueTypeURL: var property URLPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -245,7 +239,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeEmail: + case typed.PropertyValueTypeEmail: var property EmailPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -254,7 +248,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypePhoneNumber: + case typed.PropertyValueTypePhoneNumber: var property PhoneNumberPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -263,7 +257,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeCreatedTime: + case typed.PropertyValueTypeCreatedTime: var property CreatedTimePropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -272,7 +266,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeCreatedBy: + case typed.PropertyValueTypeCreatedBy: var property CreatedByPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -281,7 +275,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeLastEditedTime: + case typed.PropertyValueTypeLastEditedTime: var property LastEditedTimePropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -290,7 +284,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { p.Properties[name] = property - case PropertyValueTypeLastEditedBy: + case typed.PropertyValueTypeLastEditedBy: var property LastEditedByPropertyValue if err := json.Unmarshal(value, &property); err != nil { @@ -310,36 +304,13 @@ type PropertyValue interface { isPropertyValue() } -type PropertyValueType string - -const ( - PropertyValueTypeRichText PropertyValueType = "rich_text" - PropertyValueTypeNumber PropertyValueType = "number" - PropertyValueTypeSelect PropertyValueType = "select" - PropertyValueTypeMultiSelect PropertyValueType = "multi_select" - PropertyValueTypeDate PropertyValueType = "date" - PropertyValueTypeFormula PropertyValueType = "formula" - PropertyValueTypeRollup PropertyValueType = "rollup" - PropertyValueTypeTitle PropertyValueType = "title" - PropertyValueTypePeople PropertyValueType = "people" - PropertyValueTypeFiles PropertyValueType = "files" - PropertyValueTypeCheckbox PropertyValueType = "checkbox" - PropertyValueTypeURL PropertyValueType = "url" - PropertyValueTypeEmail PropertyValueType = "email" - PropertyValueTypePhoneNumber PropertyValueType = "phone_number" - PropertyValueTypeCreatedTime PropertyValueType = "created_time" - PropertyValueTypeCreatedBy PropertyValueType = "created_by" - PropertyValueTypeLastEditedTime PropertyValueType = "last_edited_time" - PropertyValueTypeLastEditedBy PropertyValueType = "last_edited_by" -) - type basePropertyValue struct { // Underlying identifier for the property. This identifier is guaranteed to remain constant when the property name changes. // It may be a UUID, but is often a short random string. // The id may be used in place of name when creating or updating pages. ID string `json:"id,omitempty"` // Type of the property - Type PropertyValueType `json:"type,omitempty"` + Type typed.PropertyValueType `json:"type,omitempty"` } func (p basePropertyValue) isPropertyValue() {} @@ -416,9 +387,9 @@ type NumberPropertyValue struct { } type SelectPropertyValueOption struct { - ID string `json:"id,omitempty"` - Name string `json:"name"` - Color Color `json:"color,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name"` + Color typed.Color `json:"color,omitempty"` } type SelectPropertyValue struct { @@ -427,9 +398,9 @@ type SelectPropertyValue struct { } type MultiSelectPropertyValueOption struct { - ID string `json:"id"` - Name string `json:"name"` - Color Color `json:"color"` + ID string `json:"id"` + Name string `json:"name"` + Color typed.Color `json:"color"` } type MultiSelectPropertyValue struct { @@ -457,7 +428,7 @@ func newFormulaValueType(data []byte) (FormulaValue, error) { } switch base.Type { - case FormulaValueTypeString: + case typed.FormulaValueTypeString: var formulaValue StringFormulaValue if err := json.Unmarshal(data, &formulaValue); err != nil { @@ -466,7 +437,7 @@ func newFormulaValueType(data []byte) (FormulaValue, error) { return formulaValue, nil - case FormulaValueTypeNumber: + case typed.FormulaValueTypeNumber: var formulaValue NumberFormulaValue if err := json.Unmarshal(data, &formulaValue); err != nil { @@ -475,7 +446,7 @@ func newFormulaValueType(data []byte) (FormulaValue, error) { return formulaValue, nil - case FormulaValueTypeBoolean: + case typed.FormulaValueTypeBoolean: var formulaValue BooleanFormulaValue if err := json.Unmarshal(data, &formulaValue); err != nil { @@ -484,7 +455,7 @@ func newFormulaValueType(data []byte) (FormulaValue, error) { return formulaValue, nil - case FormulaValueTypeDate: + case typed.FormulaValueTypeDate: var formulaValue DateFormulaValue if err := json.Unmarshal(data, &formulaValue); err != nil { @@ -497,17 +468,8 @@ func newFormulaValueType(data []byte) (FormulaValue, error) { return nil, ErrUnknown } -type FormulaValueType string - -const ( - FormulaValueTypeString FormulaValueType = "string" - FormulaValueTypeNumber FormulaValueType = "number" - FormulaValueTypeBoolean FormulaValueType = "boolean" - FormulaValueTypeDate FormulaValueType = "date" -) - type baseFormulaValue struct { - Type FormulaValueType `json:"type"` + Type typed.FormulaValueType `json:"type"` } func (b baseFormulaValue) isFormulaValue() {} diff --git a/search.go b/search.go index 9dabd1e..34d0899 100644 --- a/search.go +++ b/search.go @@ -4,50 +4,26 @@ import ( "context" "encoding/json" "net/http" -) - -type SearchFilterValue string - -const ( - SearchFilterValuePage SearchFilterValue = "page" - SearchFilterValueDatabase SearchFilterValue = "database" -) - -type SearchFilterProperty string -const ( - SearchFilterPropertyObject SearchFilterProperty = "object" + "github.com/mkfsn/notion-go/typed" ) type SearchFilter struct { // The value of the property to filter the results by. Possible values for object type include `page` or `database`. // Limitation: Currently the only filter allowed is object which will filter by type of `object` // (either `page` or `database`) - Value SearchFilterValue `json:"value"` + Value typed.SearchFilterValue `json:"value"` // The name of the property to filter by. Currently the only property you can filter by is the object type. // Possible values include `object`. Limitation: Currently the only filter allowed is `object` which will // filter by type of object (either `page` or `database`) - Property SearchFilterProperty `json:"property"` + Property typed.SearchFilterProperty `json:"property"` } -type SearchSortDirection string - -const ( - SearchSortDirectionAscending SearchSortDirection = "ascending" - SearchSortDirectionDescending SearchSortDirection = " descending" -) - -type SearchSortTimestamp string - -const ( - SearchSortTimestampLastEditedTime SearchSortTimestamp = "last_edited_time" -) - type SearchSort struct { // The direction to sort. - Direction SearchSortDirection `json:"direction"` + Direction typed.SearchSortDirection `json:"direction"` // The name of the timestamp to sort against. Possible values include `last_edited_time`. - Timestamp SearchSortTimestamp `json:"timestamp"` + Timestamp typed.SearchSortTimestamp `json:"timestamp"` } type SearchParameters struct { @@ -84,7 +60,7 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { for _, result := range alias.Results { var base struct { - Object ObjectType `json:"object"` + Object typed.ObjectType `json:"object"` } if err := json.Unmarshal(result, &base); err != nil { @@ -92,7 +68,7 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { } switch base.Object { - case ObjectTypePage: + case typed.ObjectTypePage: var object Page if err := json.Unmarshal(result, &object); err != nil { @@ -101,7 +77,7 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { s.Results = append(s.Results, object) - case ObjectTypeDatabase: + case typed.ObjectTypeDatabase: var object Database if err := json.Unmarshal(result, &object); err != nil { @@ -110,7 +86,7 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { s.Results = append(s.Results, object) - case ObjectTypeBlock: + case typed.ObjectTypeBlock: continue } } diff --git a/typed/block.go b/typed/block.go new file mode 100644 index 0000000..edce446 --- /dev/null +++ b/typed/block.go @@ -0,0 +1,16 @@ +package typed + +type BlockType string + +const ( + BlockTypeParagraph BlockType = "paragraph" + BlockTypeHeading1 BlockType = "heading_1" + BlockTypeHeading2 BlockType = "heading_2" + BlockTypeHeading3 BlockType = "heading_3" + BlockTypeBulletedListItem BlockType = "bulleted_list_item" + BlockTypeNumberedListItem BlockType = "numbered_list_item" + BlockTypeToDo BlockType = "to_do" + BlockTypeToggle BlockType = "toggle" + BlockTypeChildPage BlockType = "child_page" + BlockTypeUnsupported BlockType = "unsupported" +) diff --git a/typed/color.go b/typed/color.go new file mode 100644 index 0000000..c374020 --- /dev/null +++ b/typed/color.go @@ -0,0 +1,25 @@ +package typed + +type Color string + +const ( + DefaultColor Color = "default" + GrayColor Color = "gray" + BrownColor Color = "brown" + OrangeColor Color = "orange" + YellowColor Color = "yellow" + GreenColor Color = "green" + BlueColor Color = "blue" + PurpleColor Color = "purple" + PinkColor Color = "pink" + RedColor Color = "red" + GrayBackgroundColor Color = "gray_background" + BrownBackgroundColor Color = "brown_background" + OrangeBackgroundColor Color = "orange_background" + YellowBackgroundColor Color = "yellow_background" + GreenBackgroundColor Color = "green_background" + BlueBackgroundColor Color = "blue_background" + PurpleBackgroundColor Color = "purple_background" + PinkBackgroundColor Color = "pink_background" + RedBackgroundColor Color = "red_background" +) diff --git a/typed/format.go b/typed/format.go new file mode 100644 index 0000000..77106e6 --- /dev/null +++ b/typed/format.go @@ -0,0 +1,17 @@ +package typed + +type NumberFormat string + +const ( + NumberFormatNumber NumberFormat = "number" + NumberFormatNumberWithCommas NumberFormat = "number_with_commas" + NumberFormatPercent NumberFormat = "percent" + NumberFormatDollar NumberFormat = "dollar" + NumberFormatEuro NumberFormat = "euro" + NumberFormatPound NumberFormat = "pound" + NumberFormatYen NumberFormat = "yen" + NumberFormatRuble NumberFormat = "ruble" + NumberFormatRupee NumberFormat = "rupee" + NumberFormatWon NumberFormat = "won" + NumberFormatYuan NumberFormat = "yuan" +) diff --git a/typed/formula.go b/typed/formula.go new file mode 100644 index 0000000..46ce351 --- /dev/null +++ b/typed/formula.go @@ -0,0 +1,10 @@ +package typed + +type FormulaValueType string + +const ( + FormulaValueTypeString FormulaValueType = "string" + FormulaValueTypeNumber FormulaValueType = "number" + FormulaValueTypeBoolean FormulaValueType = "boolean" + FormulaValueTypeDate FormulaValueType = "date" +) diff --git a/typed/function.go b/typed/function.go new file mode 100644 index 0000000..08833ee --- /dev/null +++ b/typed/function.go @@ -0,0 +1,19 @@ +package typed + +type RollupFunction string + +const ( + RollupFunctionCountAll RollupFunction = "count_all" + RollupFunctionCountValues RollupFunction = "count_values" + RollupFunctionCountUniqueValues RollupFunction = "count_unique_values" + RollupFunctionCountEmpty RollupFunction = "count_empty" + RollupFunctionCountNotEmpty RollupFunction = "count_not_empty" + RollupFunctionPercentEmpty RollupFunction = "percent_empty" + RollupFunctionPercentNotEmpty RollupFunction = "percent_not_empty" + RollupFunctionSum RollupFunction = "sum" + RollupFunctionAverage RollupFunction = "average" + RollupFunctionMedian RollupFunction = "median" + RollupFunctionMin RollupFunction = "min" + RollupFunctionMax RollupFunction = "max" + RollupFunctionRange RollupFunction = "range" +) diff --git a/typed/object.go b/typed/object.go new file mode 100644 index 0000000..98f266d --- /dev/null +++ b/typed/object.go @@ -0,0 +1,10 @@ +package typed + +type ObjectType string + +const ( + ObjectTypeBlock ObjectType = "block" + ObjectTypePage ObjectType = "page" + ObjectTypeDatabase ObjectType = "database" + ObjectTypeList ObjectType = "list" +) diff --git a/typed/parent.go b/typed/parent.go new file mode 100644 index 0000000..0022f56 --- /dev/null +++ b/typed/parent.go @@ -0,0 +1,9 @@ +package typed + +type ParentType string + +const ( + ParentTypeDatabase ParentType = "database_id" + ParentTypePage ParentType = "page" + ParentTypeWorkspace ParentType = "workspace" +) diff --git a/typed/property.go b/typed/property.go new file mode 100644 index 0000000..8c2229b --- /dev/null +++ b/typed/property.go @@ -0,0 +1,48 @@ +package typed + +type PropertyType string + +const ( + PropertyTypeTitle PropertyType = "title" + PropertyTypeRichText PropertyType = "rich_text" + PropertyTypeNumber PropertyType = "number" + PropertyTypeSelect PropertyType = "select" + PropertyTypeMultiSelect PropertyType = "multi_select" + PropertyTypeDate PropertyType = "date" + PropertyTypePeople PropertyType = "people" + PropertyTypeFile PropertyType = "file" + PropertyTypeCheckbox PropertyType = "checkbox" + PropertyTypeURL PropertyType = "url" + PropertyTypeEmail PropertyType = "email" + PropertyTypePhoneNumber PropertyType = "phone_number" + PropertyTypeFormula PropertyType = "formula" + PropertyTypeRelation PropertyType = "relation" + PropertyTypeRollup PropertyType = "rollup" + PropertyTypeCreatedTime PropertyType = "created_time" + PropertyTypeCreatedBy PropertyType = "created_by" + PropertyTypeLastEditedTime PropertyType = "last_edited_time" + PropertyTypeLastEditedBy PropertyType = "last_edited_by" +) + +type PropertyValueType string + +const ( + PropertyValueTypeRichText PropertyValueType = "rich_text" + PropertyValueTypeNumber PropertyValueType = "number" + PropertyValueTypeSelect PropertyValueType = "select" + PropertyValueTypeMultiSelect PropertyValueType = "multi_select" + PropertyValueTypeDate PropertyValueType = "date" + PropertyValueTypeFormula PropertyValueType = "formula" + PropertyValueTypeRollup PropertyValueType = "rollup" + PropertyValueTypeTitle PropertyValueType = "title" + PropertyValueTypePeople PropertyValueType = "people" + PropertyValueTypeFiles PropertyValueType = "files" + PropertyValueTypeCheckbox PropertyValueType = "checkbox" + PropertyValueTypeURL PropertyValueType = "url" + PropertyValueTypeEmail PropertyValueType = "email" + PropertyValueTypePhoneNumber PropertyValueType = "phone_number" + PropertyValueTypeCreatedTime PropertyValueType = "created_time" + PropertyValueTypeCreatedBy PropertyValueType = "created_by" + PropertyValueTypeLastEditedTime PropertyValueType = "last_edited_time" + PropertyValueTypeLastEditedBy PropertyValueType = "last_edited_by" +) diff --git a/typed/richtext.go b/typed/richtext.go new file mode 100644 index 0000000..e3fbd0a --- /dev/null +++ b/typed/richtext.go @@ -0,0 +1,9 @@ +package typed + +type RichTextType string + +const ( + RichTextTypeText RichTextType = "text" + RichTextTypeMention RichTextType = "mention" + RichTextTypeEquation RichTextType = "equation" +) diff --git a/typed/search.go b/typed/search.go new file mode 100644 index 0000000..6a7440d --- /dev/null +++ b/typed/search.go @@ -0,0 +1,27 @@ +package typed + +type SearchFilterValue string + +const ( + SearchFilterValuePage SearchFilterValue = "page" + SearchFilterValueDatabase SearchFilterValue = "database" +) + +type SearchFilterProperty string + +const ( + SearchFilterPropertyObject SearchFilterProperty = "object" +) + +type SearchSortDirection string + +const ( + SearchSortDirectionAscending SearchSortDirection = "ascending" + SearchSortDirectionDescending SearchSortDirection = " descending" +) + +type SearchSortTimestamp string + +const ( + SearchSortTimestampLastEditedTime SearchSortTimestamp = "last_edited_time" +) diff --git a/typed/sort.go b/typed/sort.go new file mode 100644 index 0000000..c30c8e7 --- /dev/null +++ b/typed/sort.go @@ -0,0 +1,15 @@ +package typed + +type SortTimestamp string + +const ( + SortTimestampByCreatedTime SortTimestamp = "created_time" + SortTimestampByLastEditedTime SortTimestamp = "last_edited_time" +) + +type SortDirection string + +const ( + SortDirectionAscending SortDirection = "ascending" + SortDirectionDescending SortDirection = "descending" +) diff --git a/typed/user.go b/typed/user.go new file mode 100644 index 0000000..8155358 --- /dev/null +++ b/typed/user.go @@ -0,0 +1,8 @@ +package typed + +type UserType string + +const ( + UserTypePerson UserType = "person" + UserTypeBot UserType = "bot" +) diff --git a/types.go b/types.go index 9a53c60..677b057 100644 --- a/types.go +++ b/types.go @@ -1,11 +1,7 @@ package notion -type ObjectType string - -const ( - ObjectTypeBlock ObjectType = "block" - ObjectTypePage ObjectType = "page" - ObjectTypeDatabase ObjectType = "database" +import ( + "github.com/mkfsn/notion-go/typed" ) type PaginationParameters struct { @@ -17,31 +13,7 @@ type PaginationParameters struct { } type PaginatedList struct { - Object string `json:"object"` - HasMore bool `json:"has_more"` - NextCursor string `json:"next_cursor"` + Object typed.ObjectType `json:"object"` + HasMore bool `json:"has_more"` + NextCursor string `json:"next_cursor"` } - -type Color string - -const ( - DefaultColor Color = "default" - GrayColor Color = "gray" - BrownColor Color = "brown" - OrangeColor Color = "orange" - YellowColor Color = "yellow" - GreenColor Color = "green" - BlueColor Color = "blue" - PurpleColor Color = "purple" - PinkColor Color = "pink" - RedColor Color = "red" - GrayBackgroundColor Color = "gray_background" - BrownBackgroundColor Color = "brown_background" - OrangeBackgroundColor Color = "orange_background" - YellowBackgroundColor Color = "yellow_background" - GreenBackgroundColor Color = "green_background" - BlueBackgroundColor Color = "blue_background" - PurpleBackgroundColor Color = "purple_background" - PinkBackgroundColor Color = "pink_background" - RedBackgroundColor Color = "red_background" -) diff --git a/users.go b/users.go index 852a599..b7799f2 100644 --- a/users.go +++ b/users.go @@ -5,13 +5,8 @@ import ( "encoding/json" "net/http" "strings" -) - -type UserType = string -const ( - UserTypePerson UserType = "person" - UserTypeBot UserType = "bot" + "github.com/mkfsn/notion-go/typed" ) type User interface { @@ -26,7 +21,7 @@ func newUser(data []byte) (User, error) { } switch base.Type { - case UserTypePerson: + case typed.UserTypePerson: var user PersonUser if err := json.Unmarshal(data, &user); err != nil { @@ -35,7 +30,7 @@ func newUser(data []byte) (User, error) { return user, nil - case UserTypeBot: + case typed.UserTypeBot: var user BotUser if err := json.Unmarshal(data, &user); err != nil { @@ -49,11 +44,11 @@ func newUser(data []byte) (User, error) { } type baseUser struct { - Object string `json:"object"` - ID string `json:"id"` - Type UserType `json:"type"` - Name string `json:"name"` - AvatarURL string `json:"avatar_url"` + Object string `json:"object"` + ID string `json:"id"` + Type typed.UserType `json:"type"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` } func (b baseUser) isUser() {} From e9b496b529ac704f8504785de14f45df5673c7d6 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Sun, 16 May 2021 22:41:58 +0800 Subject: [PATCH 04/32] refactor: move client to rest package --- api.go | 69 +++++++++-- blocks.go | 52 ++++---- client.go | 104 ---------------- databases.go | 66 +++++----- errors.go | 23 +--- examples/append-block-children/main.go | 15 +-- examples/create-page/main.go | 15 +-- examples/list-block-children/main.go | 2 +- examples/list-databases/main.go | 2 +- examples/list-users/main.go | 2 +- examples/query-database/main.go | 5 +- examples/retrieve-database/main.go | 2 +- examples/retrieve-page/main.go | 2 +- examples/retrieve-user/main.go | 2 +- examples/search/main.go | 11 +- examples/update-page/main.go | 2 +- pages.go | 66 +++++----- rest/client.go | 162 +++++++++++++++++++++++++ rest/interface.go | 23 ++++ search.go | 27 ++--- users.go | 46 +++---- 21 files changed, 392 insertions(+), 306 deletions(-) delete mode 100644 client.go create mode 100644 rest/client.go create mode 100644 rest/interface.go diff --git a/api.go b/api.go index 402b6a9..9449cbd 100644 --- a/api.go +++ b/api.go @@ -2,6 +2,9 @@ package notion import ( "context" + "net/http" + + "github.com/mkfsn/notion-go/rest" ) const ( @@ -21,6 +24,16 @@ const ( const ( DefaultNotionVersion = "2021-05-13" + DefaultUserAgent = "mkfsn/notion-go" +) + +var ( + defaultSettings = apiSettings{ + baseURL: APIBaseURL, + notionVersion: DefaultNotionVersion, + userAgent: DefaultUserAgent, + httpClient: http.DefaultClient, + } ) type API struct { @@ -31,21 +44,26 @@ type API struct { blocksClient BlocksInterface } -func New(setters ...ClientOption) *API { - options := defaultOptions +func New(authToken string, setters ...APISetting) *API { + settings := defaultSettings for _, setter := range setters { - setter(&options) + setter(&settings) } - client := newHTTPClient(options) + restClient := rest.New(). + BearerToken(authToken). + BaseURL(settings.baseURL). + UserAgent(settings.userAgent). + Client(settings.httpClient). + Header("Notion-Version", settings.notionVersion) return &API{ - searchClient: newSearchClient(client), - usersClient: newUsersClient(client), - databasesClient: newDatabasesClient(client), - pagesClient: newPagesClient(client), - blocksClient: newBlocksClient(client), + searchClient: newSearchClient(restClient), + usersClient: newUsersClient(restClient), + databasesClient: newDatabasesClient(restClient), + pagesClient: newPagesClient(restClient), + blocksClient: newBlocksClient(restClient), } } @@ -88,3 +106,36 @@ func (c *API) Search(ctx context.Context, params SearchParameters) (*SearchRespo return c.searchClient.Search(ctx, params) } + +type apiSettings struct { + baseURL string + notionVersion string + userAgent string + httpClient *http.Client +} + +type APISetting func(o *apiSettings) + +func WithBaseURL(baseURL string) APISetting { + return func(o *apiSettings) { + o.baseURL = baseURL + } +} + +func WithNotionVersion(notionVersion string) APISetting { + return func(o *apiSettings) { + o.notionVersion = notionVersion + } +} + +func WithUserAgent(userAgent string) APISetting { + return func(o *apiSettings) { + o.userAgent = userAgent + } +} + +func WithHTTPClient(httpClient *http.Client) APISetting { + return func(o *apiSettings) { + o.httpClient = httpClient + } +} diff --git a/blocks.go b/blocks.go index 9d7cd3e..7d87af4 100644 --- a/blocks.go +++ b/blocks.go @@ -3,10 +3,10 @@ package notion import ( "context" "encoding/json" - "net/http" "strings" "time" + "github.com/mkfsn/notion-go/rest" "github.com/mkfsn/notion-go/typed" ) @@ -211,9 +211,9 @@ type blocksClient struct { childrenClient *blocksChildrenClient } -func newBlocksClient(client client) *blocksClient { +func newBlocksClient(restClient rest.Interface) *blocksClient { return &blocksClient{ - childrenClient: newBlocksChildrenClient(client), + childrenClient: newBlocksChildrenClient(restClient), } } @@ -293,45 +293,37 @@ type BlocksChildrenInterface interface { } type blocksChildrenClient struct { - client client + restClient rest.Interface } -func newBlocksChildrenClient(client client) *blocksChildrenClient { +func newBlocksChildrenClient(restClient rest.Interface) *blocksChildrenClient { return &blocksChildrenClient{ - client: client, + restClient: restClient, } } func (b *blocksChildrenClient) List(ctx context.Context, params BlocksChildrenListParameters) (*BlocksChildrenListResponse, error) { - endpoint := strings.Replace(APIBlocksListChildrenEndpoint, "{block_id}", params.BlockID, 1) + var result BlocksChildrenListResponse + var failure HTTPError - data, err := b.client.Request(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var response BlocksChildrenListResponse - - if err := json.Unmarshal(data, &response); err != nil { - return nil, err - } + err := b.restClient.New().Get(). + Endpoint(strings.Replace(APIBlocksListChildrenEndpoint, "{block_id}", params.BlockID, 1)). + QueryStruct(params). + BodyJSON(nil). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } func (b *blocksChildrenClient) Append(ctx context.Context, params BlocksChildrenAppendParameters) (*BlocksChildrenAppendResponse, error) { - endpoint := strings.Replace(APIBlocksAppendChildrenEndpoint, "{block_id}", params.BlockID, 1) + var result BlocksChildrenAppendResponse + var failure HTTPError - data, err := b.client.Request(ctx, http.MethodPatch, endpoint, params) - if err != nil { - return nil, err - } - - var response BlocksChildrenAppendResponse - - if err := json.Unmarshal(data, &response); err != nil { - return nil, err - } + err := b.restClient.New().Patch(). + Endpoint(strings.Replace(APIBlocksAppendChildrenEndpoint, "{block_id}", params.BlockID, 1)). + QueryStruct(params). + BodyJSON(params). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } diff --git a/client.go b/client.go deleted file mode 100644 index 0b8de2a..0000000 --- a/client.go +++ /dev/null @@ -1,104 +0,0 @@ -package notion - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - - "github.com/google/go-querystring/query" -) - -var ( - defaultOptions = clientOptions{ - authToken: "", - baseURL: APIBaseURL, - notionVersion: DefaultNotionVersion, - } -) - -type clientOptions struct { - authToken string - baseURL string - notionVersion string -} - -type ClientOption func(o *clientOptions) - -func WithAuthToken(authToken string) ClientOption { - return func(o *clientOptions) { - o.authToken = authToken - } -} - -func WithBaseURL(baseURL string) ClientOption { - return func(o *clientOptions) { - o.baseURL = baseURL - } -} - -func WithNotionVersion(notionVersion string) ClientOption { - return func(o *clientOptions) { - o.notionVersion = notionVersion - } -} - -type client interface { - Request(ctx context.Context, method string, endpoint string, body interface{}) ([]byte, error) -} - -type httpClient struct { - http.Client - clientOptions -} - -func newHTTPClient(clientOptions clientOptions) *httpClient { - return &httpClient{ - clientOptions: clientOptions, - } -} - -func (c *httpClient) Request(ctx context.Context, method string, endpoint string, body interface{}) ([]byte, error) { - v, err := query.Values(body) - if err != nil { - return nil, err - } - - b, err := json.Marshal(body) - if err != nil { - return nil, err - } - - req, err := http.NewRequestWithContext(ctx, method, c.baseURL+endpoint, bytes.NewBuffer(b)) - if err != nil { - return nil, err - } - - req.URL.RawQuery = v.Encode() - - if body != nil { - req.Header.Add("Content-Type", "application/json") - } - - req.Header.Add("Notion-Version", c.notionVersion) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.authToken)) - - resp, err := c.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, newHTTPError(resp.StatusCode, b) - } - - return b, nil -} diff --git a/databases.go b/databases.go index 0208bae..1a60ad0 100644 --- a/databases.go +++ b/databases.go @@ -3,10 +3,10 @@ package notion import ( "context" "encoding/json" - "net/http" "strings" "time" + "github.com/mkfsn/notion-go/rest" "github.com/mkfsn/notion-go/typed" ) @@ -761,60 +761,50 @@ type DatabasesInterface interface { } type databasesClient struct { - client client + restClient rest.Interface } -func newDatabasesClient(client client) *databasesClient { +func newDatabasesClient(restClient rest.Interface) *databasesClient { return &databasesClient{ - client: client, + restClient: restClient, } } func (d *databasesClient) Retrieve(ctx context.Context, params DatabasesRetrieveParameters) (*DatabasesRetrieveResponse, error) { - endpoint := strings.Replace(APIDatabasesRetrieveEndpoint, "{database_id}", params.DatabaseID, 1) + var result DatabasesRetrieveResponse + var failure HTTPError - b, err := d.client.Request(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var response DatabasesRetrieveResponse + err := d.restClient.New().Get(). + Endpoint(strings.Replace(APIDatabasesRetrieveEndpoint, "{database_id}", params.DatabaseID, 1)). + QueryStruct(params). + BodyJSON(nil). + Receive(ctx, &result, &failure) - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } - - return &response, nil + return &result, err } func (d *databasesClient) List(ctx context.Context, params DatabasesListParameters) (*DatabasesListResponse, error) { - b, err := d.client.Request(ctx, http.MethodGet, APIDatabasesListEndpoint, params) - if err != nil { - return nil, err - } + var result DatabasesListResponse + var failure HTTPError - var response DatabasesListResponse + err := d.restClient.New().Get(). + Endpoint(APIDatabasesListEndpoint). + QueryStruct(params). + BodyJSON(params). + Receive(ctx, &result, &failure) - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } - - return &response, nil + return &result, err } func (d *databasesClient) Query(ctx context.Context, params DatabasesQueryParameters) (*DatabasesQueryResponse, error) { - endpoint := strings.Replace(APIDatabasesQueryEndpoint, "{database_id}", params.DatabaseID, 1) - - b, err := d.client.Request(ctx, http.MethodPost, endpoint, params) - if err != nil { - return nil, err - } - - var response DatabasesQueryResponse + var result DatabasesQueryResponse + var failure HTTPError - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } + err := d.restClient.New().Post(). + Endpoint(strings.Replace(APIDatabasesQueryEndpoint, "{database_id}", params.DatabaseID, 1)). + QueryStruct(params). + BodyJSON(params). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } diff --git a/errors.go b/errors.go index 93ce2a4..7226b72 100644 --- a/errors.go +++ b/errors.go @@ -1,34 +1,19 @@ package notion import ( - "encoding/json" "errors" "fmt" ) var ( - ErrUnimplemented = errors.New("unimplemented") - ErrUnknown = errors.New("unknown") + ErrUnknown = errors.New("unknown") ) type HTTPError struct { - StatusCode int - Code string `json:"code"` - Message string `json:"message"` + Code string `json:"code"` + Message string `json:"message"` } func (e HTTPError) Error() string { - return fmt.Sprintf("StatusCode: %d, Code: %s, Message: %s", e.StatusCode, e.Code, e.Message) -} - -func newHTTPError(statusCode int, data []byte) error { - var httpError HTTPError - - if err := json.Unmarshal(data, &httpError); err != nil { - return ErrUnknown - } - - httpError.StatusCode = statusCode - - return httpError + return fmt.Sprintf("Code: %s, Message: %s", e.Code, e.Message) } diff --git a/examples/append-block-children/main.go b/examples/append-block-children/main.go index 57358eb..c0ba8b1 100644 --- a/examples/append-block-children/main.go +++ b/examples/append-block-children/main.go @@ -6,10 +6,11 @@ import ( "os" "github.com/mkfsn/notion-go" + "github.com/mkfsn/notion-go/typed" ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) resp, err := c.Blocks().Children().Append(context.Background(), notion.BlocksChildrenAppendParameters{ @@ -17,14 +18,14 @@ func main() { Children: []notion.Block{ notion.Heading2Block{ BlockBase: notion.BlockBase{ - Object: notion.ObjectTypeBlock, - Type: notion.BlockTypeHeading2, + Object: typed.ObjectTypeBlock, + Type: typed.BlockTypeHeading2, }, Heading2: notion.HeadingBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: notion.RichTextTypeText, + Type: typed.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale", @@ -36,14 +37,14 @@ func main() { notion.ParagraphBlock{ BlockBase: notion.BlockBase{ - Object: notion.ObjectTypeBlock, - Type: notion.BlockTypeParagraph, + Object: typed.ObjectTypeBlock, + Type: typed.BlockTypeParagraph, }, Paragraph: notion.RichTextBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: notion.RichTextTypeText, + Type: typed.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", diff --git a/examples/create-page/main.go b/examples/create-page/main.go index 5b10823..9fabee0 100644 --- a/examples/create-page/main.go +++ b/examples/create-page/main.go @@ -6,10 +6,11 @@ import ( "os" "github.com/mkfsn/notion-go" + "github.com/mkfsn/notion-go/typed" ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) page, err := c.Pages().Create(context.Background(), notion.PagesCreateParameters{ @@ -44,14 +45,14 @@ func main() { Children: []notion.Block{ notion.Heading2Block{ BlockBase: notion.BlockBase{ - Object: notion.ObjectTypeBlock, - Type: notion.BlockTypeHeading2, + Object: typed.ObjectTypeBlock, + Type: typed.BlockTypeHeading2, }, Heading2: notion.HeadingBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: notion.RichTextTypeText, + Type: typed.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale", @@ -63,14 +64,14 @@ func main() { notion.ParagraphBlock{ BlockBase: notion.BlockBase{ - Object: notion.ObjectTypeBlock, - Type: notion.BlockTypeParagraph, + Object: typed.ObjectTypeBlock, + Type: typed.BlockTypeParagraph, }, Paragraph: notion.RichTextBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: notion.RichTextTypeText, + Type: typed.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", diff --git a/examples/list-block-children/main.go b/examples/list-block-children/main.go index dd5b436..7fbacee 100644 --- a/examples/list-block-children/main.go +++ b/examples/list-block-children/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) resp, err := c.Blocks().Children().List(context.Background(), notion.BlocksChildrenListParameters{ BlockID: "12e1d803ee234651a125c6ce13ccd58d"}, diff --git a/examples/list-databases/main.go b/examples/list-databases/main.go index bf3d5df..c646a8e 100644 --- a/examples/list-databases/main.go +++ b/examples/list-databases/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) resp, err := c.Databases().List(context.Background(), notion.DatabasesListParameters{ PaginationParameters: notion.PaginationParameters{ diff --git a/examples/list-users/main.go b/examples/list-users/main.go index 32fbce1..22a4b6e 100644 --- a/examples/list-users/main.go +++ b/examples/list-users/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) resp, err := c.Users().List(context.Background(), notion.UsersListParameters{ PaginationParameters: notion.PaginationParameters{ diff --git a/examples/query-database/main.go b/examples/query-database/main.go index 087c8e3..4c27fd1 100644 --- a/examples/query-database/main.go +++ b/examples/query-database/main.go @@ -6,10 +6,11 @@ import ( "os" "github.com/mkfsn/notion-go" + "github.com/mkfsn/notion-go/typed" ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) keyword := "medium.com" @@ -30,7 +31,7 @@ func main() { Sorts: []notion.Sort{ { Property: "Created", - Direction: notion.SortDirectionAscending, + Direction: typed.SortDirectionAscending, }, }, }) diff --git a/examples/retrieve-database/main.go b/examples/retrieve-database/main.go index cc591ad..91009dc 100644 --- a/examples/retrieve-database/main.go +++ b/examples/retrieve-database/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) resp, err := c.Databases().Retrieve(context.Background(), notion.DatabasesRetrieveParameters{ DatabaseID: "def72422-ea36-4c8a-a6f1-a34e11a7fe54", diff --git a/examples/retrieve-page/main.go b/examples/retrieve-page/main.go index 5fac798..84b91ba 100644 --- a/examples/retrieve-page/main.go +++ b/examples/retrieve-page/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) page, err := c.Pages().Retrieve(context.Background(), notion.PagesRetrieveParameters{ PageID: "676aa7b7-2bba-4b5b-9fd6-b43f5543482d"}, diff --git a/examples/retrieve-user/main.go b/examples/retrieve-user/main.go index f01be7d..4d5d0c2 100644 --- a/examples/retrieve-user/main.go +++ b/examples/retrieve-user/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) resp, err := c.Users().Retrieve(context.Background(), notion.UsersRetrieveParameters{UserID: "8cd69bf3-1532-43d2-9b11-9803c813d607"}) diff --git a/examples/search/main.go b/examples/search/main.go index 7a6a0dc..1b04142 100644 --- a/examples/search/main.go +++ b/examples/search/main.go @@ -6,20 +6,21 @@ import ( "os" "github.com/mkfsn/notion-go" + "github.com/mkfsn/notion-go/typed" ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) resp, err := c.Search(context.Background(), notion.SearchParameters{ Query: "フィリスのアトリエ", Sort: notion.SearchSort{ - Direction: notion.SearchSortDirectionAscending, - Timestamp: notion.SearchSortTimestampLastEditedTime, + Direction: typed.SearchSortDirectionAscending, + Timestamp: typed.SearchSortTimestampLastEditedTime, }, Filter: notion.SearchFilter{ - Property: notion.SearchFilterPropertyObject, - Value: notion.SearchFilterValuePage, + Property: typed.SearchFilterPropertyObject, + Value: typed.SearchFilterValuePage, }, }) if err != nil { diff --git a/examples/update-page/main.go b/examples/update-page/main.go index 73baa21..ed60565 100644 --- a/examples/update-page/main.go +++ b/examples/update-page/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - c := notion.New(notion.WithAuthToken(os.Getenv("NOTION_AUTH_TOKEN"))) + c := notion.New(os.Getenv("NOTION_AUTH_TOKEN")) page, err := c.Pages().Update(context.Background(), notion.PagesUpdateParameters{ diff --git a/pages.go b/pages.go index 4e6082f..d8e71d7 100644 --- a/pages.go +++ b/pages.go @@ -3,10 +3,10 @@ package notion import ( "context" "encoding/json" - "net/http" "strings" "time" + "github.com/mkfsn/notion-go/rest" "github.com/mkfsn/notion-go/typed" ) @@ -642,60 +642,50 @@ type PagesInterface interface { } type pagesClient struct { - client client + restClient rest.Interface } -func newPagesClient(client client) *pagesClient { +func newPagesClient(restClient rest.Interface) *pagesClient { return &pagesClient{ - client: client, + restClient: restClient, } } func (p *pagesClient) Retrieve(ctx context.Context, params PagesRetrieveParameters) (*PagesRetrieveResponse, error) { - endpoint := strings.Replace(APIPagesRetrieveEndpoint, "{page_id}", params.PageID, 1) + var result PagesRetrieveResponse + var failure HTTPError - b, err := p.client.Request(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var response PagesRetrieveResponse - - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } + err := p.restClient.New().Get(). + Endpoint(strings.Replace(APIPagesRetrieveEndpoint, "{page_id}", params.PageID, 1)). + QueryStruct(params). + BodyJSON(nil). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } func (p *pagesClient) Update(ctx context.Context, params PagesUpdateParameters) (*PagesUpdateResponse, error) { - endpoint := strings.Replace(APIPagesUpdateEndpoint, "{page_id}", params.PageID, 1) - - b, err := p.client.Request(ctx, http.MethodPatch, endpoint, params) - if err != nil { - return nil, err - } + var result PagesUpdateResponse + var failure HTTPError - var response PagesUpdateResponse + err := p.restClient.New().Patch(). + Endpoint(strings.Replace(APIPagesUpdateEndpoint, "{page_id}", params.PageID, 1)). + QueryStruct(params). + BodyJSON(params). + Receive(ctx, &result, &failure) - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } - - return &response, nil + return &result, err } func (p *pagesClient) Create(ctx context.Context, params PagesCreateParameters) (*PagesCreateResponse, error) { - b, err := p.client.Request(ctx, http.MethodPost, APIPagesCreateEndpoint, params) - if err != nil { - return nil, err - } - - var response PagesCreateResponse + var result PagesCreateResponse + var failure HTTPError - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } + err := p.restClient.New().Post(). + Endpoint(APIPagesCreateEndpoint). + QueryStruct(params). + BodyJSON(params). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } diff --git a/rest/client.go b/rest/client.go new file mode 100644 index 0000000..e384b12 --- /dev/null +++ b/rest/client.go @@ -0,0 +1,162 @@ +package rest + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/google/go-querystring/query" +) + +type restClient struct { + baseURL string + header http.Header + httpClient *http.Client + + method string + endpoint string + queryStruct interface{} + bodyJSON interface{} +} + +func New() Interface { + return &restClient{ + header: make(http.Header), + httpClient: http.DefaultClient, + } +} + +func (r *restClient) New() Interface { + newRestClient := &restClient{ + baseURL: r.baseURL, + header: r.header.Clone(), + httpClient: r.httpClient, // TODO: deep copy + } + return newRestClient +} + +func (r *restClient) BearerToken(token string) Interface { + r.header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + return r +} + +func (r *restClient) BaseURL(baseURL string) Interface { + r.baseURL = baseURL + return r +} + +func (r *restClient) Client(httpClient *http.Client) Interface { + r.httpClient = httpClient + return r +} + +func (r *restClient) UserAgent(userAgent string) Interface { + r.header.Set("User-Agent", userAgent) + return r +} + +func (r *restClient) Header(key, value string) Interface { + r.header.Set(key, value) + return r +} + +func (r *restClient) Get() Interface { + r.method = http.MethodGet + return r +} + +func (r *restClient) Post() Interface { + r.method = http.MethodPost + return r +} + +func (r *restClient) Patch() Interface { + r.method = http.MethodPatch + return r +} + +func (r *restClient) Endpoint(endpoint string) Interface { + r.endpoint = endpoint + return r +} + +func (r *restClient) QueryStruct(queryStruct interface{}) Interface { + r.queryStruct = queryStruct + return r +} + +func (r *restClient) BodyJSON(bodyJSON interface{}) Interface { + r.bodyJSON = bodyJSON + + if r.bodyJSON != nil { + r.header.Add("Content-Type", "application/json") + } + + return r +} + +func (r *restClient) Request(ctx context.Context) (*http.Request, error) { + v, err := query.Values(r.queryStruct) + if err != nil { + return nil, err + } + + b, err := json.Marshal(r.bodyJSON) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, r.method, r.baseURL+r.endpoint, bytes.NewBuffer(b)) + if err != nil { + return nil, err + } + + req.URL.RawQuery = v.Encode() + + req.Header = r.header + + return req, nil +} + +func (r *restClient) Receive(ctx context.Context, success, failure interface{}) error { + req, err := r.Request(ctx) + if err != nil { + return err + } + + resp, err := r.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + return r.decodeResponseData(resp.StatusCode, b, success, failure) +} + +func (r *restClient) decodeResponseData(statusCode int, data []byte, success, failure interface{}) error { + if statusCode == http.StatusOK { + if success != nil { + return json.Unmarshal(data, success) + } + + return nil + } + + if failure == nil { + return nil + } + + if err := json.Unmarshal(data, failure); err != nil { + return err + } + + return failure.(error) +} diff --git a/rest/interface.go b/rest/interface.go new file mode 100644 index 0000000..71fa770 --- /dev/null +++ b/rest/interface.go @@ -0,0 +1,23 @@ +package rest + +import ( + "context" + "net/http" +) + +type Interface interface { + New() Interface + BearerToken(token string) Interface + BaseURL(baseURL string) Interface + Client(httpClient *http.Client) Interface + UserAgent(userAgent string) Interface + Header(key, value string) Interface + Get() Interface + Post() Interface + Patch() Interface + Endpoint(endpoint string) Interface + QueryStruct(queryStruct interface{}) Interface + BodyJSON(bodyJSON interface{}) Interface + Request(ctx context.Context) (*http.Request, error) + Receive(ctx context.Context, success, failure interface{}) error +} diff --git a/search.go b/search.go index 34d0899..3470b87 100644 --- a/search.go +++ b/search.go @@ -3,8 +3,8 @@ package notion import ( "context" "encoding/json" - "net/http" + "github.com/mkfsn/notion-go/rest" "github.com/mkfsn/notion-go/typed" ) @@ -99,26 +99,25 @@ type SearchInterface interface { } type searchClient struct { - client client + restClient rest.Interface } -func newSearchClient(client client) *searchClient { +func newSearchClient(restClient rest.Interface) *searchClient { return &searchClient{ - client: client, + restClient: restClient, } } func (s *searchClient) Search(ctx context.Context, params SearchParameters) (*SearchResponse, error) { - b, err := s.client.Request(ctx, http.MethodPost, APISearchEndpoint, params) - if err != nil { - return nil, err - } - - var response SearchResponse + var result SearchResponse + var failure HTTPError - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } + err := s.restClient.New(). + Post(). + Endpoint(APISearchEndpoint). + QueryStruct(params). + BodyJSON(params). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } diff --git a/users.go b/users.go index b7799f2..44c1b9c 100644 --- a/users.go +++ b/users.go @@ -3,9 +3,9 @@ package notion import ( "context" "encoding/json" - "net/http" "strings" + "github.com/mkfsn/notion-go/rest" "github.com/mkfsn/notion-go/typed" ) @@ -121,43 +121,37 @@ type UsersInterface interface { } type usersClient struct { - client client + restClient rest.Interface } -func newUsersClient(client client) *usersClient { +func newUsersClient(restClient rest.Interface) *usersClient { return &usersClient{ - client: client, + restClient: restClient, } } func (u *usersClient) Retrieve(ctx context.Context, params UsersRetrieveParameters) (*UsersRetrieveResponse, error) { - endpoint := strings.Replace(APIUsersRetrieveEndpoint, "{user_id}", params.UserID, 1) + var result UsersRetrieveResponse + var failure HTTPError - b, err := u.client.Request(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var response UsersRetrieveResponse - - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } + err := u.restClient.New().Get(). + Endpoint(strings.Replace(APIUsersRetrieveEndpoint, "{user_id}", params.UserID, 1)). + QueryStruct(params). + BodyJSON(nil). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } func (u *usersClient) List(ctx context.Context, params UsersListParameters) (*UsersListResponse, error) { - b, err := u.client.Request(ctx, http.MethodGet, APIUsersListEndpoint, params) - if err != nil { - return nil, err - } - - var response UsersListResponse + var result UsersListResponse + var failure HTTPError - if err := json.Unmarshal(b, &response); err != nil { - return nil, err - } + err := u.restClient.New().Get(). + Endpoint(APIUsersListEndpoint). + QueryStruct(params). + BodyJSON(params). + Receive(ctx, &result, &failure) - return &response, nil + return &result, err } From 186a9332920fac21ec84800c11aaebeddb34c89b Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 09:52:00 +0800 Subject: [PATCH 05/32] refactor: introduce decoder --- blocks.go | 215 ++++++++--------- databases.go | 366 +++++++++------------------- examples/list-users/main.go | 2 +- examples/retrieve-user/main.go | 2 +- examples/search/main.go | 4 +- pages.go | 422 +++++++++++---------------------- search.go | 62 +++-- users.go | 90 +++---- 8 files changed, 430 insertions(+), 733 deletions(-) diff --git a/blocks.go b/blocks.go index 7d87af4..a38ad12 100644 --- a/blocks.go +++ b/blocks.go @@ -14,110 +14,6 @@ type Block interface { isBlock() } -// FIXME: reduce function length -// nolint:funlen -func newBlock(data []byte) (Block, error) { - var base BlockBase - - if err := json.Unmarshal(data, &base); err != nil { - return nil, err - } - - switch base.Type { - case typed.BlockTypeParagraph: - var block ParagraphBlock - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeHeading1: - var block Heading1Block - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeHeading2: - var block Heading2Block - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeHeading3: - var block Heading3Block - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeBulletedListItem: - var block BulletedListItemBlock - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeNumberedListItem: - var block NumberedListItemBlock - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeToDo: - var block ToDoBlock - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeToggle: - var block ToggleBlock - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeChildPage: - var block ChildPageBlock - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - - case typed.BlockTypeUnsupported: - var block UnsupportedBlock - - if err := json.Unmarshal(data, &block); err != nil { - return nil, err - } - - return block, nil - } - - return nil, ErrUnknown -} - type BlockBase struct { // Always "block". Object typed.ObjectType `json:"object"` @@ -144,6 +40,22 @@ type HeadingBlock struct { Text []RichText `json:"text"` } +func (h *HeadingBlock) UnmarshalJSON(data []byte) error { + var text []richTextDecoder + + if err := json.Unmarshal(data, &text); err != nil { + return nil + } + + h.Text = make([]RichText, 0, len(text)) + + for _, decoder := range text { + h.Text = append(h.Text, decoder.RichText) + } + + return nil +} + type Heading1Block struct { BlockBase Heading1 HeadingBlock `json:"heading_1"` @@ -160,8 +72,33 @@ type Heading3Block struct { } type RichTextBlock struct { - Text []RichText `json:"text"` - Children []BlockBase `json:"children,omitempty"` + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +func (r *RichTextBlock) UnmarshalJSON(data []byte) error { + var alias struct { + Text []richTextDecoder `json:"text"` + Children []blockDecoder `json:"children"` + } + + if err := json.Unmarshal(data, &alias); err != nil { + return nil + } + + r.Text = make([]RichText, 0, len(r.Text)) + + for _, decoder := range alias.Text { + r.Text = append(r.Text, decoder.RichText) + } + + r.Children = make([]Block, 0, len(r.Children)) + + for _, decoder := range alias.Children { + r.Children = append(r.Children, decoder.Block) + } + + return nil } type BulletedListItemBlock struct { @@ -242,7 +179,7 @@ func (b *BlocksChildrenListResponse) UnmarshalJSON(data []byte) error { alias := struct { *Alias - Results []json.RawMessage `json:"results"` + Results []blockDecoder `json:"results"` }{ Alias: (*Alias)(b), } @@ -253,13 +190,8 @@ func (b *BlocksChildrenListResponse) UnmarshalJSON(data []byte) error { b.Results = make([]Block, 0, len(alias.Results)) - for _, value := range alias.Results { - block, err := newBlock(value) - if err != nil { - return err - } - - b.Results = append(b.Results, block) + for _, decoder := range alias.Results { + b.Results = append(b.Results, decoder.Block) } return nil @@ -277,12 +209,13 @@ type BlocksChildrenAppendResponse struct { } func (b *BlocksChildrenAppendResponse) UnmarshalJSON(data []byte) error { - block, err := newBlock(data) - if err != nil { + var decoder blockDecoder + + if err := json.Unmarshal(data, &decoder); err != nil { return err } - b.Block = block + b.Block = decoder.Block return nil } @@ -327,3 +260,51 @@ func (b *blocksChildrenClient) Append(ctx context.Context, params BlocksChildren return &result, err } + +type blockDecoder struct { + Block +} + +func (b *blockDecoder) UnmarshalJSON(data []byte) error { + var decoder struct { + Type typed.BlockType `json:"type"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Type { + case typed.BlockTypeParagraph: + b.Block = &ParagraphBlock{} + + case typed.BlockTypeHeading1: + b.Block = &Heading1Block{} + + case typed.BlockTypeHeading2: + b.Block = &Heading2Block{} + + case typed.BlockTypeHeading3: + b.Block = &Heading3Block{} + + case typed.BlockTypeBulletedListItem: + b.Block = &BulletedListItemBlock{} + + case typed.BlockTypeNumberedListItem: + b.Block = &NumberedListItemBlock{} + + case typed.BlockTypeToDo: + b.Block = &ToDoBlock{} + + case typed.BlockTypeToggle: + b.Block = &ToggleBlock{} + + case typed.BlockTypeChildPage: + b.Block = &ChildPageBlock{} + + case typed.BlockTypeUnsupported: + b.Block = &UnsupportedBlock{} + } + + return json.Unmarshal(data, &b.Block) +} diff --git a/databases.go b/databases.go index 1a60ad0..67e0029 100644 --- a/databases.go +++ b/databases.go @@ -22,15 +22,13 @@ type Database struct { func (d Database) isSearchable() {} -// FIXME: reduce the complexity -// nolint:gocyclo,gocognit,funlen func (d *Database) UnmarshalJSON(data []byte) error { type Alias Database alias := struct { *Alias - Title []json.RawMessage `json:"title"` - Properties map[string]json.RawMessage `json:"properties"` + Title []richTextDecoder `json:"title"` + Properties map[string]propertyDecoder `json:"properties"` }{ Alias: (*Alias)(d), } @@ -40,224 +38,15 @@ func (d *Database) UnmarshalJSON(data []byte) error { } d.Title = make([]RichText, 0, len(alias.Title)) - d.Properties = make(map[string]Property) - - for _, title := range alias.Title { - var base BaseRichText - - if err := json.Unmarshal(title, &base); err != nil { - return err - } - - switch base.Type { - case typed.RichTextTypeText: - var richText RichTextText - - if err := json.Unmarshal(title, &richText); err != nil { - return err - } - - d.Title = append(d.Title, richText) - - case typed.RichTextTypeMention: - var richText RichTextMention - - if err := json.Unmarshal(title, &richText); err != nil { - return err - } - - d.Title = append(d.Title, richText) - case typed.RichTextTypeEquation: - var richText RichTextEquation - - if err := json.Unmarshal(title, &richText); err != nil { - return err - } - - d.Title = append(d.Title, richText) - } + for _, decoder := range alias.Title { + d.Title = append(d.Title, decoder.RichText) } - for name, value := range alias.Properties { - var base baseProperty - - if err := json.Unmarshal(value, &base); err != nil { - return err - } - - switch base.Type { - case typed.PropertyTypeTitle: - var property TitleProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeRichText: - var property RichTextProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeNumber: - var property NumberProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeSelect: - var property SelectProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeMultiSelect: - var property MultiSelectProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeDate: - var property DateProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypePeople: - var property PeopleProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeFile: - var property FileProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeCheckbox: - var property CheckboxProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeURL: - var property URLProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeEmail: - var property EmailProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypePhoneNumber: - var property PhoneNumberProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeFormula: - var property FormulaProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeRelation: - var property RelationProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeRollup: - var property RollupProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeCreatedTime: - var property CreatedTimeProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeCreatedBy: - var property CreatedByProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeLastEditedTime: - var property LastEditedTimeProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - d.Properties[name] = property - - case typed.PropertyTypeLastEditedBy: - var property LastEditedByProperty - - if err := json.Unmarshal(value, &property); err != nil { - return err - } + d.Properties = make(map[string]Property) - d.Properties[name] = property - } + for name, decoder := range alias.Properties { + d.Properties[name] = decoder.Property } return nil @@ -282,45 +71,6 @@ type RichText interface { isRichText() } -func newRichText(data []byte) (RichText, error) { - var base BaseRichText - - if err := json.Unmarshal(data, &base); err != nil { - return nil, err - } - - switch base.Type { - case typed.RichTextTypeText: - var richText RichTextText - - if err := json.Unmarshal(data, &richText); err != nil { - return nil, err - } - - return richText, nil - - case typed.RichTextTypeMention: - var richText RichTextMention - - if err := json.Unmarshal(data, &richText); err != nil { - return nil, err - } - - return richText, nil - - case typed.RichTextTypeEquation: - var richText RichTextEquation - - if err := json.Unmarshal(data, &richText); err != nil { - return nil, err - } - - return richText, nil - } - - return nil, ErrUnknown -} - type BaseRichText struct { // The plain text without annotations. PlainText string `json:"plain_text,omitempty"` @@ -808,3 +558,105 @@ func (d *databasesClient) Query(ctx context.Context, params DatabasesQueryParame return &result, err } + +type richTextDecoder struct { + RichText +} + +func (r *richTextDecoder) UnmarshalJSON(data []byte) error { + var decoder struct { + Type typed.RichTextType `json:"type"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Type { + case typed.RichTextTypeText: + r.RichText = &RichTextText{} + + case typed.RichTextTypeMention: + r.RichText = &RichTextMention{} + + case typed.RichTextTypeEquation: + r.RichText = &RichTextEquation{} + } + + return json.Unmarshal(data, &r.RichText) +} + +type propertyDecoder struct { + Property +} + +func (p *propertyDecoder) Unmarshal(data []byte) error { + var decoder struct { + Type typed.PropertyType `json:"type"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Type { + case typed.PropertyTypeTitle: + p.Property = &TitleProperty{} + + case typed.PropertyTypeRichText: + p.Property = &RichTextProperty{} + + case typed.PropertyTypeNumber: + p.Property = &NumberProperty{} + + case typed.PropertyTypeSelect: + p.Property = &SelectProperty{} + + case typed.PropertyTypeMultiSelect: + p.Property = &MultiSelectProperty{} + + case typed.PropertyTypeDate: + p.Property = &DateProperty{} + + case typed.PropertyTypePeople: + p.Property = &PeopleProperty{} + + case typed.PropertyTypeFile: + p.Property = &FileProperty{} + + case typed.PropertyTypeCheckbox: + p.Property = &CheckboxProperty{} + + case typed.PropertyTypeURL: + p.Property = &URLProperty{} + + case typed.PropertyTypeEmail: + p.Property = &EmailProperty{} + + case typed.PropertyTypePhoneNumber: + p.Property = &PhoneNumberProperty{} + + case typed.PropertyTypeFormula: + p.Property = &FormulaProperty{} + + case typed.PropertyTypeRelation: + p.Property = &RelationProperty{} + + case typed.PropertyTypeRollup: + p.Property = &RollupProperty{} + + case typed.PropertyTypeCreatedTime: + p.Property = &CreatedTimeProperty{} + + case typed.PropertyTypeCreatedBy: + p.Property = &CreatedByProperty{} + + case typed.PropertyTypeLastEditedTime: + p.Property = &LastEditedTimeProperty{} + + case typed.PropertyTypeLastEditedBy: + p.Property = &LastEditedByProperty{} + } + + return json.Unmarshal(data, &p.Property) +} diff --git a/examples/list-users/main.go b/examples/list-users/main.go index 22a4b6e..2e04a3b 100644 --- a/examples/list-users/main.go +++ b/examples/list-users/main.go @@ -14,7 +14,7 @@ func main() { resp, err := c.Users().List(context.Background(), notion.UsersListParameters{ PaginationParameters: notion.PaginationParameters{ StartCursor: "", - PageSize: 1, + PageSize: 10, }, }) diff --git a/examples/retrieve-user/main.go b/examples/retrieve-user/main.go index 4d5d0c2..a6d5ecd 100644 --- a/examples/retrieve-user/main.go +++ b/examples/retrieve-user/main.go @@ -17,5 +17,5 @@ func main() { log.Fatal(err) } - log.Printf("%#v\n", resp) + log.Printf("%#v\n", resp.User) } diff --git a/examples/search/main.go b/examples/search/main.go index 1b04142..e95f678 100644 --- a/examples/search/main.go +++ b/examples/search/main.go @@ -27,5 +27,7 @@ func main() { log.Fatalf("error: %s\n", err) } - log.Printf("response: %#v\n", resp) + for _, object := range resp.Results { + log.Printf("object: %#v\n", object) + } } diff --git a/pages.go b/pages.go index d8e71d7..4232627 100644 --- a/pages.go +++ b/pages.go @@ -69,15 +69,13 @@ type Page struct { Archived bool `json:"archived"` } -// FIXME: reduce the complexity -// nolint:gocyclo,gocognit,funlen func (p *Page) UnmarshalJSON(data []byte) error { type Alias Page alias := struct { *Alias - Parent json.RawMessage `json:"parent"` - Properties map[string]json.RawMessage `json:"properties"` + Parent parentDecoder `json:"parent"` + Properties map[string]propertyValueDecoder `json:"properties"` }{ Alias: (*Alias)(p), } @@ -86,213 +84,12 @@ func (p *Page) UnmarshalJSON(data []byte) error { return err } - var baseParent baseParent - - if err := json.Unmarshal(alias.Parent, &baseParent); err != nil { - return err - } - - switch baseParent.Type { - case typed.ParentTypeDatabase: - var parent DatabaseParent - - if err := json.Unmarshal(alias.Parent, &parent); err != nil { - return err - } - - p.Parent = parent - - case typed.ParentTypePage: - var parent PageParent - - if err := json.Unmarshal(alias.Parent, &parent); err != nil { - return err - } - - p.Parent = parent - - case typed.ParentTypeWorkspace: - var parent WorkspaceParent - - if err := json.Unmarshal(alias.Parent, &parent); err != nil { - return err - } - - p.Parent = parent - } + p.Parent = alias.Parent.Parent p.Properties = make(map[string]PropertyValue) - for name, value := range alias.Properties { - var base basePropertyValue - - if err := json.Unmarshal(value, &base); err != nil { - return err - } - - switch base.Type { - case typed.PropertyValueTypeRichText: - var property RichTextPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeNumber: - var property NumberPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeSelect: - var property SelectPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeMultiSelect: - var property MultiSelectPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeDate: - var property DatePropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeFormula: - var property FormulaPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeRollup: - var property RollupPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeTitle: - var property TitlePropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypePeople: - var property PeoplePropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeFiles: - var property FilesPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeCheckbox: - var property CheckboxPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeURL: - var property URLPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeEmail: - var property EmailPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypePhoneNumber: - var property PhoneNumberPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeCreatedTime: - var property CreatedTimePropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeCreatedBy: - var property CreatedByPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeLastEditedTime: - var property LastEditedTimePropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - - case typed.PropertyValueTypeLastEditedBy: - var property LastEditedByPropertyValue - - if err := json.Unmarshal(value, &property); err != nil { - return err - } - - p.Properties[name] = property - } + for name, decoder := range alias.Properties { + p.Properties[name] = decoder.PropertyValue } return nil @@ -325,7 +122,7 @@ func (t *TitlePropertyValue) UnmarshalJSON(data []byte) error { alias := struct { *Alias - Title []json.RawMessage `json:"title"` + Title []richTextDecoder `json:"title"` }{ Alias: (*Alias)(t), } @@ -336,13 +133,8 @@ func (t *TitlePropertyValue) UnmarshalJSON(data []byte) error { t.Title = make([]RichText, 0, len(alias.Title)) - for _, value := range alias.Title { - richText, err := newRichText(value) - if err != nil { - return err - } - - t.Title = append(t.Title, richText) + for _, decoder := range alias.Title { + t.Title = append(t.Title, decoder.RichText) } return nil @@ -358,7 +150,7 @@ func (r *RichTextPropertyValue) UnmarshalJSON(data []byte) error { alias := struct { *Alias - RichText []json.RawMessage `json:"rich_text"` + RichText []richTextDecoder `json:"rich_text"` }{ Alias: (*Alias)(r), } @@ -369,13 +161,8 @@ func (r *RichTextPropertyValue) UnmarshalJSON(data []byte) error { r.RichText = make([]RichText, 0, len(alias.RichText)) - for _, value := range alias.RichText { - richText, err := newRichText(value) - if err != nil { - return err - } - - r.RichText = append(r.RichText, richText) + for _, decoder := range alias.RichText { + r.RichText = append(r.RichText, decoder.RichText) } return nil @@ -420,54 +207,6 @@ type FormulaValue interface { isFormulaValue() } -func newFormulaValueType(data []byte) (FormulaValue, error) { - var base baseFormulaValue - - if err := json.Unmarshal(data, &base); err != nil { - return nil, err - } - - switch base.Type { - case typed.FormulaValueTypeString: - var formulaValue StringFormulaValue - - if err := json.Unmarshal(data, &formulaValue); err != nil { - return nil, err - } - - return formulaValue, nil - - case typed.FormulaValueTypeNumber: - var formulaValue NumberFormulaValue - - if err := json.Unmarshal(data, &formulaValue); err != nil { - return nil, err - } - - return formulaValue, nil - - case typed.FormulaValueTypeBoolean: - var formulaValue BooleanFormulaValue - - if err := json.Unmarshal(data, &formulaValue); err != nil { - return nil, err - } - - return formulaValue, nil - - case typed.FormulaValueTypeDate: - var formulaValue DateFormulaValue - - if err := json.Unmarshal(data, &formulaValue); err != nil { - return nil, err - } - - return formulaValue, nil - } - - return nil, ErrUnknown -} - type baseFormulaValue struct { Type typed.FormulaValueType `json:"type"` } @@ -504,7 +243,7 @@ func (f *FormulaPropertyValue) UnmarshalJSON(data []byte) error { alias := struct { *Alias - Formula json.RawMessage `json:"formula"` + Formula formulaValueDecoder `json:"formula"` }{ Alias: (*Alias)(f), } @@ -513,12 +252,7 @@ func (f *FormulaPropertyValue) UnmarshalJSON(data []byte) error { return err } - formula, err := newFormulaValueType(alias.Formula) - if err != nil { - return err - } - - f.Formula = formula + f.Formula = alias.Formula.FormulaValue return nil } @@ -689,3 +423,133 @@ func (p *pagesClient) Create(ctx context.Context, params PagesCreateParameters) return &result, err } + +type formulaValueDecoder struct { + FormulaValue +} + +func (f *formulaValueDecoder) UnmarshalJSON(data []byte) error { + var decoder struct { + Type typed.FormulaValueType `json:"type"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Type { + case typed.FormulaValueTypeString: + f.FormulaValue = &StringFormulaValue{} + + case typed.FormulaValueTypeNumber: + f.FormulaValue = &NumberFormulaValue{} + + case typed.FormulaValueTypeBoolean: + f.FormulaValue = &BooleanFormulaValue{} + + case typed.FormulaValueTypeDate: + f.FormulaValue = &DateFormulaValue{} + } + + return json.Unmarshal(data, &f.FormulaValue) +} + +type parentDecoder struct { + Parent +} + +func (p *parentDecoder) UnmarshalJSON(data []byte) error { + var decoder struct { + Type typed.ParentType `json:"type"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Type { + case typed.ParentTypeDatabase: + p.Parent = &DatabaseParent{} + + case typed.ParentTypePage: + p.Parent = &PageParent{} + + case typed.ParentTypeWorkspace: + p.Parent = &WorkspaceParent{} + } + + return json.Unmarshal(data, p.Parent) +} + +type propertyValueDecoder struct { + PropertyValue +} + +func (p *propertyValueDecoder) UnmarshalJSON(data []byte) error { + var decoder struct { + Type typed.PropertyValueType `json:"type,omitempty"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Type { + case typed.PropertyValueTypeRichText: + p.PropertyValue = &RichTextPropertyValue{} + + case typed.PropertyValueTypeNumber: + p.PropertyValue = &NumberPropertyValue{} + + case typed.PropertyValueTypeSelect: + p.PropertyValue = &SelectPropertyValue{} + + case typed.PropertyValueTypeMultiSelect: + p.PropertyValue = &MultiSelectPropertyValue{} + + case typed.PropertyValueTypeDate: + p.PropertyValue = &DatePropertyValue{} + + case typed.PropertyValueTypeFormula: + p.PropertyValue = &FormulaPropertyValue{} + + case typed.PropertyValueTypeRollup: + p.PropertyValue = &RollupPropertyValue{} + + case typed.PropertyValueTypeTitle: + p.PropertyValue = &TitlePropertyValue{} + + case typed.PropertyValueTypePeople: + p.PropertyValue = &PeoplePropertyValue{} + + case typed.PropertyValueTypeFiles: + p.PropertyValue = &FilesPropertyValue{} + + case typed.PropertyValueTypeCheckbox: + p.PropertyValue = &CheckboxPropertyValue{} + + case typed.PropertyValueTypeURL: + p.PropertyValue = &URLPropertyValue{} + + case typed.PropertyValueTypeEmail: + p.PropertyValue = &EmailPropertyValue{} + + case typed.PropertyValueTypePhoneNumber: + p.PropertyValue = &PhoneNumberPropertyValue{} + + case typed.PropertyValueTypeCreatedTime: + p.PropertyValue = &CreatedTimePropertyValue{} + + case typed.PropertyValueTypeCreatedBy: + p.PropertyValue = &CreatedByPropertyValue{} + + case typed.PropertyValueTypeLastEditedTime: + p.PropertyValue = &LastEditedTimePropertyValue{} + + case typed.PropertyValueTypeLastEditedBy: + p.PropertyValue = &LastEditedByPropertyValue{} + + } + + return json.Unmarshal(data, p.PropertyValue) +} diff --git a/search.go b/search.go index 3470b87..878de62 100644 --- a/search.go +++ b/search.go @@ -47,7 +47,7 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { alias := struct { *Alias - Results []json.RawMessage `json:"results"` + Results []searchableObjectDecoder `json:"results"` }{ Alias: (*Alias)(s), } @@ -58,37 +58,8 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { s.Results = make([]SearchableObject, 0, len(alias.Results)) - for _, result := range alias.Results { - var base struct { - Object typed.ObjectType `json:"object"` - } - - if err := json.Unmarshal(result, &base); err != nil { - return err - } - - switch base.Object { - case typed.ObjectTypePage: - var object Page - - if err := json.Unmarshal(result, &object); err != nil { - return err - } - - s.Results = append(s.Results, object) - - case typed.ObjectTypeDatabase: - var object Database - - if err := json.Unmarshal(result, &object); err != nil { - return err - } - - s.Results = append(s.Results, object) - - case typed.ObjectTypeBlock: - continue - } + for _, decoder := range alias.Results { + s.Results = append(s.Results, decoder.SearchableObject) } return nil @@ -121,3 +92,30 @@ func (s *searchClient) Search(ctx context.Context, params SearchParameters) (*Se return &result, err } + +type searchableObjectDecoder struct { + SearchableObject +} + +func (s *searchableObjectDecoder) UnmarshalJSON(data []byte) error { + var decoder struct { + Object typed.ObjectType `json:"object"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Object { + case typed.ObjectTypePage: + s.SearchableObject = &Page{} + + case typed.ObjectTypeDatabase: + s.SearchableObject = &Database{} + + case typed.ObjectTypeBlock: + return ErrUnknown + } + + return json.Unmarshal(data, s.SearchableObject) +} diff --git a/users.go b/users.go index 44c1b9c..a60901b 100644 --- a/users.go +++ b/users.go @@ -13,36 +13,6 @@ type User interface { isUser() } -func newUser(data []byte) (User, error) { - var base baseUser - - if err := json.Unmarshal(data, &base); err != nil { - return nil, err - } - - switch base.Type { - case typed.UserTypePerson: - var user PersonUser - - if err := json.Unmarshal(data, &user); err != nil { - return nil, err - } - - return user, nil - - case typed.UserTypeBot: - var user BotUser - - if err := json.Unmarshal(data, &user); err != nil { - return nil, err - } - - return user, nil - } - - return nil, ErrUnknown -} - type baseUser struct { Object string `json:"object"` ID string `json:"id"` @@ -53,20 +23,24 @@ type baseUser struct { func (b baseUser) isUser() {} +type Person struct { + Email string `json:"email"` +} + type PersonUser struct { baseUser - Person *struct { - Email string `json:"email"` - } `json:"person"` + Person Person `json:"person"` } +type Bot struct{} + type BotUser struct { baseUser - Bot interface{} `json:"bot"` + Bot Bot `json:"bot"` } type UsersRetrieveParameters struct { - UserID string `json:"-"` + UserID string `json:"-" url:"-"` } type UsersRetrieveResponse struct { @@ -74,8 +48,15 @@ type UsersRetrieveResponse struct { } func (u *UsersRetrieveResponse) UnmarshalJSON(data []byte) (err error) { - u.User, err = newUser(data) - return + var decoder userDecoder + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + u.User = decoder.User + + return nil } type UsersListParameters struct { @@ -92,7 +73,7 @@ func (u *UsersListResponse) UnmarshalJSON(data []byte) error { alias := struct { *Alias - Results []json.RawMessage `json:"results"` + Results []userDecoder `json:"results"` }{ Alias: (*Alias)(u), } @@ -103,13 +84,8 @@ func (u *UsersListResponse) UnmarshalJSON(data []byte) error { u.Results = make([]User, 0, len(alias.Results)) - for _, result := range alias.Results { - user, err := newUser(result) - if err != nil { - return err - } - - u.Results = append(u.Results, user) + for _, decoder := range alias.Results { + u.Results = append(u.Results, decoder.User) } return nil @@ -155,3 +131,27 @@ func (u *usersClient) List(ctx context.Context, params UsersListParameters) (*Us return &result, err } + +type userDecoder struct { + User +} + +func (u *userDecoder) UnmarshalJSON(data []byte) error { + var decoder struct { + Type typed.UserType `json:"type"` + } + + if err := json.Unmarshal(data, &decoder); err != nil { + return err + } + + switch decoder.Type { + case typed.UserTypePerson: + u.User = &PersonUser{} + + case typed.UserTypeBot: + u.User = &BotUser{} + } + + return json.Unmarshal(data, u.User) +} From 775d96cec389ee9a1b70a78259ee56ad8e1e8208 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 09:52:54 +0800 Subject: [PATCH 06/32] refactor: rename types to pagination --- types.go => pagination.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename types.go => pagination.go (100%) diff --git a/types.go b/pagination.go similarity index 100% rename from types.go rename to pagination.go From 57c1f3c933330ecabf5158141f411deb9ec43d85 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 10:01:19 +0800 Subject: [PATCH 07/32] refactor: move back typed to notion package --- blocks.go | 27 ++-- consts.go | 202 +++++++++++++++++++++++++ databases.go | 93 ++++++------ examples/append-block-children/main.go | 13 +- examples/create-page/main.go | 13 +- examples/query-database/main.go | 3 +- examples/search/main.go | 9 +- pages.go | 77 +++++----- pagination.go | 10 +- search.go | 17 +-- typed/block.go | 16 -- typed/color.go | 25 --- typed/format.go | 17 --- typed/formula.go | 10 -- typed/function.go | 19 --- typed/object.go | 10 -- typed/parent.go | 9 -- typed/property.go | 48 ------ typed/richtext.go | 9 -- typed/search.go | 27 ---- typed/sort.go | 15 -- typed/user.go | 8 - users.go | 17 +-- 23 files changed, 335 insertions(+), 359 deletions(-) create mode 100644 consts.go delete mode 100644 typed/block.go delete mode 100644 typed/color.go delete mode 100644 typed/format.go delete mode 100644 typed/formula.go delete mode 100644 typed/function.go delete mode 100644 typed/object.go delete mode 100644 typed/parent.go delete mode 100644 typed/property.go delete mode 100644 typed/richtext.go delete mode 100644 typed/search.go delete mode 100644 typed/sort.go delete mode 100644 typed/user.go diff --git a/blocks.go b/blocks.go index a38ad12..b45a3ff 100644 --- a/blocks.go +++ b/blocks.go @@ -7,7 +7,6 @@ import ( "time" "github.com/mkfsn/notion-go/rest" - "github.com/mkfsn/notion-go/typed" ) type Block interface { @@ -16,11 +15,11 @@ type Block interface { type BlockBase struct { // Always "block". - Object typed.ObjectType `json:"object"` + Object ObjectType `json:"object"` // Identifier for the block. ID string `json:"id,omitempty"` // Type of block. - Type typed.BlockType `json:"type"` + Type BlockType `json:"type"` // Date and time when this block was created. Formatted as an ISO 8601 date time string. CreatedTime *time.Time `json:"created_time,omitempty"` // Date and time when this block was last updated. Formatted as an ISO 8601 date time string. @@ -267,7 +266,7 @@ type blockDecoder struct { func (b *blockDecoder) UnmarshalJSON(data []byte) error { var decoder struct { - Type typed.BlockType `json:"type"` + Type BlockType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -275,34 +274,34 @@ func (b *blockDecoder) UnmarshalJSON(data []byte) error { } switch decoder.Type { - case typed.BlockTypeParagraph: + case BlockTypeParagraph: b.Block = &ParagraphBlock{} - case typed.BlockTypeHeading1: + case BlockTypeHeading1: b.Block = &Heading1Block{} - case typed.BlockTypeHeading2: + case BlockTypeHeading2: b.Block = &Heading2Block{} - case typed.BlockTypeHeading3: + case BlockTypeHeading3: b.Block = &Heading3Block{} - case typed.BlockTypeBulletedListItem: + case BlockTypeBulletedListItem: b.Block = &BulletedListItemBlock{} - case typed.BlockTypeNumberedListItem: + case BlockTypeNumberedListItem: b.Block = &NumberedListItemBlock{} - case typed.BlockTypeToDo: + case BlockTypeToDo: b.Block = &ToDoBlock{} - case typed.BlockTypeToggle: + case BlockTypeToggle: b.Block = &ToggleBlock{} - case typed.BlockTypeChildPage: + case BlockTypeChildPage: b.Block = &ChildPageBlock{} - case typed.BlockTypeUnsupported: + case BlockTypeUnsupported: b.Block = &UnsupportedBlock{} } diff --git a/consts.go b/consts.go new file mode 100644 index 0000000..e3611a1 --- /dev/null +++ b/consts.go @@ -0,0 +1,202 @@ +package notion + +type BlockType string + +const ( + BlockTypeParagraph BlockType = "paragraph" + BlockTypeHeading1 BlockType = "heading_1" + BlockTypeHeading2 BlockType = "heading_2" + BlockTypeHeading3 BlockType = "heading_3" + BlockTypeBulletedListItem BlockType = "bulleted_list_item" + BlockTypeNumberedListItem BlockType = "numbered_list_item" + BlockTypeToDo BlockType = "to_do" + BlockTypeToggle BlockType = "toggle" + BlockTypeChildPage BlockType = "child_page" + BlockTypeUnsupported BlockType = "unsupported" +) + +type Color string + +const ( + DefaultColor Color = "default" + GrayColor Color = "gray" + BrownColor Color = "brown" + OrangeColor Color = "orange" + YellowColor Color = "yellow" + GreenColor Color = "green" + BlueColor Color = "blue" + PurpleColor Color = "purple" + PinkColor Color = "pink" + RedColor Color = "red" + GrayBackgroundColor Color = "gray_background" + BrownBackgroundColor Color = "brown_background" + OrangeBackgroundColor Color = "orange_background" + YellowBackgroundColor Color = "yellow_background" + GreenBackgroundColor Color = "green_background" + BlueBackgroundColor Color = "blue_background" + PurpleBackgroundColor Color = "purple_background" + PinkBackgroundColor Color = "pink_background" + RedBackgroundColor Color = "red_background" +) + +type NumberFormat string + +const ( + NumberFormatNumber NumberFormat = "number" + NumberFormatNumberWithCommas NumberFormat = "number_with_commas" + NumberFormatPercent NumberFormat = "percent" + NumberFormatDollar NumberFormat = "dollar" + NumberFormatEuro NumberFormat = "euro" + NumberFormatPound NumberFormat = "pound" + NumberFormatYen NumberFormat = "yen" + NumberFormatRuble NumberFormat = "ruble" + NumberFormatRupee NumberFormat = "rupee" + NumberFormatWon NumberFormat = "won" + NumberFormatYuan NumberFormat = "yuan" +) + +type FormulaValueType string + +const ( + FormulaValueTypeString FormulaValueType = "string" + FormulaValueTypeNumber FormulaValueType = "number" + FormulaValueTypeBoolean FormulaValueType = "boolean" + FormulaValueTypeDate FormulaValueType = "date" +) + +type RollupFunction string + +const ( + RollupFunctionCountAll RollupFunction = "count_all" + RollupFunctionCountValues RollupFunction = "count_values" + RollupFunctionCountUniqueValues RollupFunction = "count_unique_values" + RollupFunctionCountEmpty RollupFunction = "count_empty" + RollupFunctionCountNotEmpty RollupFunction = "count_not_empty" + RollupFunctionPercentEmpty RollupFunction = "percent_empty" + RollupFunctionPercentNotEmpty RollupFunction = "percent_not_empty" + RollupFunctionSum RollupFunction = "sum" + RollupFunctionAverage RollupFunction = "average" + RollupFunctionMedian RollupFunction = "median" + RollupFunctionMin RollupFunction = "min" + RollupFunctionMax RollupFunction = "max" + RollupFunctionRange RollupFunction = "range" +) + +type ObjectType string + +const ( + ObjectTypeBlock ObjectType = "block" + ObjectTypePage ObjectType = "page" + ObjectTypeDatabase ObjectType = "database" + ObjectTypeList ObjectType = "list" +) + +type ParentType string + +const ( + ParentTypeDatabase ParentType = "database_id" + ParentTypePage ParentType = "page" + ParentTypeWorkspace ParentType = "workspace" +) + +type PropertyType string + +const ( + PropertyTypeTitle PropertyType = "title" + PropertyTypeRichText PropertyType = "rich_text" + PropertyTypeNumber PropertyType = "number" + PropertyTypeSelect PropertyType = "select" + PropertyTypeMultiSelect PropertyType = "multi_select" + PropertyTypeDate PropertyType = "date" + PropertyTypePeople PropertyType = "people" + PropertyTypeFile PropertyType = "file" + PropertyTypeCheckbox PropertyType = "checkbox" + PropertyTypeURL PropertyType = "url" + PropertyTypeEmail PropertyType = "email" + PropertyTypePhoneNumber PropertyType = "phone_number" + PropertyTypeFormula PropertyType = "formula" + PropertyTypeRelation PropertyType = "relation" + PropertyTypeRollup PropertyType = "rollup" + PropertyTypeCreatedTime PropertyType = "created_time" + PropertyTypeCreatedBy PropertyType = "created_by" + PropertyTypeLastEditedTime PropertyType = "last_edited_time" + PropertyTypeLastEditedBy PropertyType = "last_edited_by" +) + +type PropertyValueType string + +const ( + PropertyValueTypeRichText PropertyValueType = "rich_text" + PropertyValueTypeNumber PropertyValueType = "number" + PropertyValueTypeSelect PropertyValueType = "select" + PropertyValueTypeMultiSelect PropertyValueType = "multi_select" + PropertyValueTypeDate PropertyValueType = "date" + PropertyValueTypeFormula PropertyValueType = "formula" + PropertyValueTypeRollup PropertyValueType = "rollup" + PropertyValueTypeTitle PropertyValueType = "title" + PropertyValueTypePeople PropertyValueType = "people" + PropertyValueTypeFiles PropertyValueType = "files" + PropertyValueTypeCheckbox PropertyValueType = "checkbox" + PropertyValueTypeURL PropertyValueType = "url" + PropertyValueTypeEmail PropertyValueType = "email" + PropertyValueTypePhoneNumber PropertyValueType = "phone_number" + PropertyValueTypeCreatedTime PropertyValueType = "created_time" + PropertyValueTypeCreatedBy PropertyValueType = "created_by" + PropertyValueTypeLastEditedTime PropertyValueType = "last_edited_time" + PropertyValueTypeLastEditedBy PropertyValueType = "last_edited_by" +) + +type RichTextType string + +const ( + RichTextTypeText RichTextType = "text" + RichTextTypeMention RichTextType = "mention" + RichTextTypeEquation RichTextType = "equation" +) + +type SearchFilterValue string + +const ( + SearchFilterValuePage SearchFilterValue = "page" + SearchFilterValueDatabase SearchFilterValue = "database" +) + +type SearchFilterProperty string + +const ( + SearchFilterPropertyObject SearchFilterProperty = "object" +) + +type SearchSortDirection string + +const ( + SearchSortDirectionAscending SearchSortDirection = "ascending" + SearchSortDirectionDescending SearchSortDirection = " descending" +) + +type SearchSortTimestamp string + +const ( + SearchSortTimestampLastEditedTime SearchSortTimestamp = "last_edited_time" +) + +type SortTimestamp string + +const ( + SortTimestampByCreatedTime SortTimestamp = "created_time" + SortTimestampByLastEditedTime SortTimestamp = "last_edited_time" +) + +type SortDirection string + +const ( + SortDirectionAscending SortDirection = "ascending" + SortDirectionDescending SortDirection = "descending" +) + +type UserType string + +const ( + UserTypePerson UserType = "person" + UserTypeBot UserType = "bot" +) diff --git a/databases.go b/databases.go index 67e0029..19d3e9c 100644 --- a/databases.go +++ b/databases.go @@ -7,12 +7,11 @@ import ( "time" "github.com/mkfsn/notion-go/rest" - "github.com/mkfsn/notion-go/typed" ) type Database struct { - Object typed.ObjectType `json:"object"` - ID string `json:"id"` + Object ObjectType `json:"object"` + ID string `json:"id"` CreatedTime time.Time `json:"created_time"` LastEditedTime time.Time `json:"last_edited_time"` @@ -64,7 +63,7 @@ type Annotations struct { // Whether the text is `code style`. Code bool `json:"code"` // Color of the text. - Color typed.Color `json:"color"` + Color Color `json:"color"` } type RichText interface { @@ -77,7 +76,7 @@ type BaseRichText struct { // (Optional) The URL of any link or internal Notion mention in this text, if any. Href string `json:"href,omitempty"` // Type of this rich text object. - Type typed.RichTextType `json:"type"` + Type RichTextType `json:"type"` // All annotations that apply to this rich text. // Annotations include colors and bold/italics/underline/strikethrough. Annotations *Annotations `json:"annotations,omitempty"` @@ -158,7 +157,7 @@ type baseProperty struct { // For example, all Title properties have an ID of "title". ID string `json:"id"` // Type that controls the behavior of the property - Type typed.PropertyType `json:"type"` + Type PropertyType `json:"type"` } func (p baseProperty) isProperty() {} @@ -174,7 +173,7 @@ type RichTextProperty struct { } type NumberPropertyOption struct { - Format typed.NumberFormat `json:"format"` + Format NumberFormat `json:"format"` } type NumberProperty struct { @@ -183,15 +182,15 @@ type NumberProperty struct { } type SelectOption struct { - Name string `json:"name"` - ID string `json:"id"` - Color typed.Color `json:"color"` + Name string `json:"name"` + ID string `json:"id"` + Color Color `json:"color"` } type MultiSelectOption struct { - Name string `json:"name"` - ID string `json:"id"` - Color typed.Color `json:"color"` + Name string `json:"name"` + ID string `json:"id"` + Color Color `json:"color"` } type SelectPropertyOption struct { @@ -264,11 +263,11 @@ type RelationProperty struct { } type RollupPropertyOption struct { - RelationPropertyName string `json:"relation_property_name"` - RelationPropertyID string `json:"relation_property_id"` - RollupPropertyName string `json:"rollup_property_name"` - RollupPropertyID string `json:"rollup_property_id"` - Function typed.RollupFunction `json:"function"` + RelationPropertyName string `json:"relation_property_name"` + RelationPropertyID string `json:"relation_property_id"` + RollupPropertyName string `json:"rollup_property_name"` + RollupPropertyID string `json:"rollup_property_id"` + Function RollupFunction `json:"function"` } type RollupProperty struct { @@ -314,9 +313,9 @@ type DatabasesListResponse struct { } type Sort struct { - Property string `json:"property,omitempty"` - Timestamp typed.SortTimestamp `json:"timestamp,omitempty"` - Direction typed.SortDirection `json:"direction,omitempty"` + Property string `json:"property,omitempty"` + Timestamp SortTimestamp `json:"timestamp,omitempty"` + Direction SortDirection `json:"direction,omitempty"` } type Filter interface { @@ -492,10 +491,10 @@ type DatabasesQueryParameters struct { // Identifier for a Notion database. DatabaseID string `json:"-"` // When supplied, limits which pages are returned based on the - // [filter conditions](https://developers.notion.com/reference-link/post-database-query-filter). + // [filter conditions](https://developers.com/reference-link/post-database-query-filter). Filter Filter `json:"filter,omitempty"` // When supplied, orders the results based on the provided - // [sort criteria](https://developers.notion.com/reference-link/post-database-query-sort). + // [sort criteria](https://developers.com/reference-link/post-database-query-sort). Sorts []Sort `json:"sorts,omitempty"` } @@ -565,7 +564,7 @@ type richTextDecoder struct { func (r *richTextDecoder) UnmarshalJSON(data []byte) error { var decoder struct { - Type typed.RichTextType `json:"type"` + Type RichTextType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -573,13 +572,13 @@ func (r *richTextDecoder) UnmarshalJSON(data []byte) error { } switch decoder.Type { - case typed.RichTextTypeText: + case RichTextTypeText: r.RichText = &RichTextText{} - case typed.RichTextTypeMention: + case RichTextTypeMention: r.RichText = &RichTextMention{} - case typed.RichTextTypeEquation: + case RichTextTypeEquation: r.RichText = &RichTextEquation{} } @@ -592,7 +591,7 @@ type propertyDecoder struct { func (p *propertyDecoder) Unmarshal(data []byte) error { var decoder struct { - Type typed.PropertyType `json:"type"` + Type PropertyType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -600,61 +599,61 @@ func (p *propertyDecoder) Unmarshal(data []byte) error { } switch decoder.Type { - case typed.PropertyTypeTitle: + case PropertyTypeTitle: p.Property = &TitleProperty{} - case typed.PropertyTypeRichText: + case PropertyTypeRichText: p.Property = &RichTextProperty{} - case typed.PropertyTypeNumber: + case PropertyTypeNumber: p.Property = &NumberProperty{} - case typed.PropertyTypeSelect: + case PropertyTypeSelect: p.Property = &SelectProperty{} - case typed.PropertyTypeMultiSelect: + case PropertyTypeMultiSelect: p.Property = &MultiSelectProperty{} - case typed.PropertyTypeDate: + case PropertyTypeDate: p.Property = &DateProperty{} - case typed.PropertyTypePeople: + case PropertyTypePeople: p.Property = &PeopleProperty{} - case typed.PropertyTypeFile: + case PropertyTypeFile: p.Property = &FileProperty{} - case typed.PropertyTypeCheckbox: + case PropertyTypeCheckbox: p.Property = &CheckboxProperty{} - case typed.PropertyTypeURL: + case PropertyTypeURL: p.Property = &URLProperty{} - case typed.PropertyTypeEmail: + case PropertyTypeEmail: p.Property = &EmailProperty{} - case typed.PropertyTypePhoneNumber: + case PropertyTypePhoneNumber: p.Property = &PhoneNumberProperty{} - case typed.PropertyTypeFormula: + case PropertyTypeFormula: p.Property = &FormulaProperty{} - case typed.PropertyTypeRelation: + case PropertyTypeRelation: p.Property = &RelationProperty{} - case typed.PropertyTypeRollup: + case PropertyTypeRollup: p.Property = &RollupProperty{} - case typed.PropertyTypeCreatedTime: + case PropertyTypeCreatedTime: p.Property = &CreatedTimeProperty{} - case typed.PropertyTypeCreatedBy: + case PropertyTypeCreatedBy: p.Property = &CreatedByProperty{} - case typed.PropertyTypeLastEditedTime: + case PropertyTypeLastEditedTime: p.Property = &LastEditedTimeProperty{} - case typed.PropertyTypeLastEditedBy: + case PropertyTypeLastEditedBy: p.Property = &LastEditedByProperty{} } diff --git a/examples/append-block-children/main.go b/examples/append-block-children/main.go index c0ba8b1..58791d6 100644 --- a/examples/append-block-children/main.go +++ b/examples/append-block-children/main.go @@ -6,7 +6,6 @@ import ( "os" "github.com/mkfsn/notion-go" - "github.com/mkfsn/notion-go/typed" ) func main() { @@ -18,14 +17,14 @@ func main() { Children: []notion.Block{ notion.Heading2Block{ BlockBase: notion.BlockBase{ - Object: typed.ObjectTypeBlock, - Type: typed.BlockTypeHeading2, + Object: notion.ObjectTypeBlock, + Type: notion.BlockTypeHeading2, }, Heading2: notion.HeadingBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: typed.RichTextTypeText, + Type: notion.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale", @@ -37,14 +36,14 @@ func main() { notion.ParagraphBlock{ BlockBase: notion.BlockBase{ - Object: typed.ObjectTypeBlock, - Type: typed.BlockTypeParagraph, + Object: notion.ObjectTypeBlock, + Type: notion.BlockTypeParagraph, }, Paragraph: notion.RichTextBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: typed.RichTextTypeText, + Type: notion.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", diff --git a/examples/create-page/main.go b/examples/create-page/main.go index 9fabee0..d54d267 100644 --- a/examples/create-page/main.go +++ b/examples/create-page/main.go @@ -6,7 +6,6 @@ import ( "os" "github.com/mkfsn/notion-go" - "github.com/mkfsn/notion-go/typed" ) func main() { @@ -45,14 +44,14 @@ func main() { Children: []notion.Block{ notion.Heading2Block{ BlockBase: notion.BlockBase{ - Object: typed.ObjectTypeBlock, - Type: typed.BlockTypeHeading2, + Object: notion.ObjectTypeBlock, + Type: notion.BlockTypeHeading2, }, Heading2: notion.HeadingBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: typed.RichTextTypeText, + Type: notion.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale", @@ -64,14 +63,14 @@ func main() { notion.ParagraphBlock{ BlockBase: notion.BlockBase{ - Object: typed.ObjectTypeBlock, - Type: typed.BlockTypeParagraph, + Object: notion.ObjectTypeBlock, + Type: notion.BlockTypeParagraph, }, Paragraph: notion.RichTextBlock{ Text: []notion.RichText{ notion.RichTextText{ BaseRichText: notion.BaseRichText{ - Type: typed.RichTextTypeText, + Type: notion.RichTextTypeText, }, Text: notion.TextObject{ Content: "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", diff --git a/examples/query-database/main.go b/examples/query-database/main.go index 4c27fd1..cb7434a 100644 --- a/examples/query-database/main.go +++ b/examples/query-database/main.go @@ -6,7 +6,6 @@ import ( "os" "github.com/mkfsn/notion-go" - "github.com/mkfsn/notion-go/typed" ) func main() { @@ -31,7 +30,7 @@ func main() { Sorts: []notion.Sort{ { Property: "Created", - Direction: typed.SortDirectionAscending, + Direction: notion.SortDirectionAscending, }, }, }) diff --git a/examples/search/main.go b/examples/search/main.go index e95f678..77ec37e 100644 --- a/examples/search/main.go +++ b/examples/search/main.go @@ -6,7 +6,6 @@ import ( "os" "github.com/mkfsn/notion-go" - "github.com/mkfsn/notion-go/typed" ) func main() { @@ -15,12 +14,12 @@ func main() { resp, err := c.Search(context.Background(), notion.SearchParameters{ Query: "フィリスのアトリエ", Sort: notion.SearchSort{ - Direction: typed.SearchSortDirectionAscending, - Timestamp: typed.SearchSortTimestampLastEditedTime, + Direction: notion.SearchSortDirectionAscending, + Timestamp: notion.SearchSortTimestampLastEditedTime, }, Filter: notion.SearchFilter{ - Property: typed.SearchFilterPropertyObject, - Value: typed.SearchFilterValuePage, + Property: notion.SearchFilterPropertyObject, + Value: notion.SearchFilterValuePage, }, }) if err != nil { diff --git a/pages.go b/pages.go index 4232627..856d681 100644 --- a/pages.go +++ b/pages.go @@ -7,7 +7,6 @@ import ( "time" "github.com/mkfsn/notion-go/rest" - "github.com/mkfsn/notion-go/typed" ) type Parent interface { @@ -15,7 +14,7 @@ type Parent interface { } type baseParent struct { - Type typed.ParentType `json:"type"` + Type ParentType `json:"type"` } func (b baseParent) isParent() {} @@ -54,7 +53,7 @@ type PageParentInput struct { type Page struct { // Always "page". - Object typed.ObjectType `json:"object"` + Object ObjectType `json:"object"` // Unique identifier of the page. ID string `json:"id"` // The page's parent @@ -107,7 +106,7 @@ type basePropertyValue struct { // The id may be used in place of name when creating or updating pages. ID string `json:"id,omitempty"` // Type of the property - Type typed.PropertyValueType `json:"type,omitempty"` + Type PropertyValueType `json:"type,omitempty"` } func (p basePropertyValue) isPropertyValue() {} @@ -174,9 +173,9 @@ type NumberPropertyValue struct { } type SelectPropertyValueOption struct { - ID string `json:"id,omitempty"` - Name string `json:"name"` - Color typed.Color `json:"color,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name"` + Color Color `json:"color,omitempty"` } type SelectPropertyValue struct { @@ -185,9 +184,9 @@ type SelectPropertyValue struct { } type MultiSelectPropertyValueOption struct { - ID string `json:"id"` - Name string `json:"name"` - Color typed.Color `json:"color"` + ID string `json:"id"` + Name string `json:"name"` + Color Color `json:"color"` } type MultiSelectPropertyValue struct { @@ -208,7 +207,7 @@ type FormulaValue interface { } type baseFormulaValue struct { - Type typed.FormulaValueType `json:"type"` + Type FormulaValueType `json:"type"` } func (b baseFormulaValue) isFormulaValue() {} @@ -430,7 +429,7 @@ type formulaValueDecoder struct { func (f *formulaValueDecoder) UnmarshalJSON(data []byte) error { var decoder struct { - Type typed.FormulaValueType `json:"type"` + Type FormulaValueType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -438,16 +437,16 @@ func (f *formulaValueDecoder) UnmarshalJSON(data []byte) error { } switch decoder.Type { - case typed.FormulaValueTypeString: + case FormulaValueTypeString: f.FormulaValue = &StringFormulaValue{} - case typed.FormulaValueTypeNumber: + case FormulaValueTypeNumber: f.FormulaValue = &NumberFormulaValue{} - case typed.FormulaValueTypeBoolean: + case FormulaValueTypeBoolean: f.FormulaValue = &BooleanFormulaValue{} - case typed.FormulaValueTypeDate: + case FormulaValueTypeDate: f.FormulaValue = &DateFormulaValue{} } @@ -460,7 +459,7 @@ type parentDecoder struct { func (p *parentDecoder) UnmarshalJSON(data []byte) error { var decoder struct { - Type typed.ParentType `json:"type"` + Type ParentType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -468,13 +467,13 @@ func (p *parentDecoder) UnmarshalJSON(data []byte) error { } switch decoder.Type { - case typed.ParentTypeDatabase: + case ParentTypeDatabase: p.Parent = &DatabaseParent{} - case typed.ParentTypePage: + case ParentTypePage: p.Parent = &PageParent{} - case typed.ParentTypeWorkspace: + case ParentTypeWorkspace: p.Parent = &WorkspaceParent{} } @@ -487,7 +486,7 @@ type propertyValueDecoder struct { func (p *propertyValueDecoder) UnmarshalJSON(data []byte) error { var decoder struct { - Type typed.PropertyValueType `json:"type,omitempty"` + Type PropertyValueType `json:"type,omitempty"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -495,58 +494,58 @@ func (p *propertyValueDecoder) UnmarshalJSON(data []byte) error { } switch decoder.Type { - case typed.PropertyValueTypeRichText: + case PropertyValueTypeRichText: p.PropertyValue = &RichTextPropertyValue{} - case typed.PropertyValueTypeNumber: + case PropertyValueTypeNumber: p.PropertyValue = &NumberPropertyValue{} - case typed.PropertyValueTypeSelect: + case PropertyValueTypeSelect: p.PropertyValue = &SelectPropertyValue{} - case typed.PropertyValueTypeMultiSelect: + case PropertyValueTypeMultiSelect: p.PropertyValue = &MultiSelectPropertyValue{} - case typed.PropertyValueTypeDate: + case PropertyValueTypeDate: p.PropertyValue = &DatePropertyValue{} - case typed.PropertyValueTypeFormula: + case PropertyValueTypeFormula: p.PropertyValue = &FormulaPropertyValue{} - case typed.PropertyValueTypeRollup: + case PropertyValueTypeRollup: p.PropertyValue = &RollupPropertyValue{} - case typed.PropertyValueTypeTitle: + case PropertyValueTypeTitle: p.PropertyValue = &TitlePropertyValue{} - case typed.PropertyValueTypePeople: + case PropertyValueTypePeople: p.PropertyValue = &PeoplePropertyValue{} - case typed.PropertyValueTypeFiles: + case PropertyValueTypeFiles: p.PropertyValue = &FilesPropertyValue{} - case typed.PropertyValueTypeCheckbox: + case PropertyValueTypeCheckbox: p.PropertyValue = &CheckboxPropertyValue{} - case typed.PropertyValueTypeURL: + case PropertyValueTypeURL: p.PropertyValue = &URLPropertyValue{} - case typed.PropertyValueTypeEmail: + case PropertyValueTypeEmail: p.PropertyValue = &EmailPropertyValue{} - case typed.PropertyValueTypePhoneNumber: + case PropertyValueTypePhoneNumber: p.PropertyValue = &PhoneNumberPropertyValue{} - case typed.PropertyValueTypeCreatedTime: + case PropertyValueTypeCreatedTime: p.PropertyValue = &CreatedTimePropertyValue{} - case typed.PropertyValueTypeCreatedBy: + case PropertyValueTypeCreatedBy: p.PropertyValue = &CreatedByPropertyValue{} - case typed.PropertyValueTypeLastEditedTime: + case PropertyValueTypeLastEditedTime: p.PropertyValue = &LastEditedTimePropertyValue{} - case typed.PropertyValueTypeLastEditedBy: + case PropertyValueTypeLastEditedBy: p.PropertyValue = &LastEditedByPropertyValue{} } diff --git a/pagination.go b/pagination.go index 677b057..79a0d55 100644 --- a/pagination.go +++ b/pagination.go @@ -1,9 +1,5 @@ package notion -import ( - "github.com/mkfsn/notion-go/typed" -) - type PaginationParameters struct { // If supplied, this endpoint will return a page of results starting after the cursor provided. // If not supplied, this endpoint will return the first page of results. @@ -13,7 +9,7 @@ type PaginationParameters struct { } type PaginatedList struct { - Object typed.ObjectType `json:"object"` - HasMore bool `json:"has_more"` - NextCursor string `json:"next_cursor"` + Object ObjectType `json:"object"` + HasMore bool `json:"has_more"` + NextCursor string `json:"next_cursor"` } diff --git a/search.go b/search.go index 878de62..b242ff9 100644 --- a/search.go +++ b/search.go @@ -5,25 +5,24 @@ import ( "encoding/json" "github.com/mkfsn/notion-go/rest" - "github.com/mkfsn/notion-go/typed" ) type SearchFilter struct { // The value of the property to filter the results by. Possible values for object type include `page` or `database`. // Limitation: Currently the only filter allowed is object which will filter by type of `object` // (either `page` or `database`) - Value typed.SearchFilterValue `json:"value"` + Value SearchFilterValue `json:"value"` // The name of the property to filter by. Currently the only property you can filter by is the object type. // Possible values include `object`. Limitation: Currently the only filter allowed is `object` which will // filter by type of object (either `page` or `database`) - Property typed.SearchFilterProperty `json:"property"` + Property SearchFilterProperty `json:"property"` } type SearchSort struct { // The direction to sort. - Direction typed.SearchSortDirection `json:"direction"` + Direction SearchSortDirection `json:"direction"` // The name of the timestamp to sort against. Possible values include `last_edited_time`. - Timestamp typed.SearchSortTimestamp `json:"timestamp"` + Timestamp SearchSortTimestamp `json:"timestamp"` } type SearchParameters struct { @@ -99,7 +98,7 @@ type searchableObjectDecoder struct { func (s *searchableObjectDecoder) UnmarshalJSON(data []byte) error { var decoder struct { - Object typed.ObjectType `json:"object"` + Object ObjectType `json:"object"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -107,13 +106,13 @@ func (s *searchableObjectDecoder) UnmarshalJSON(data []byte) error { } switch decoder.Object { - case typed.ObjectTypePage: + case ObjectTypePage: s.SearchableObject = &Page{} - case typed.ObjectTypeDatabase: + case ObjectTypeDatabase: s.SearchableObject = &Database{} - case typed.ObjectTypeBlock: + case ObjectTypeBlock: return ErrUnknown } diff --git a/typed/block.go b/typed/block.go deleted file mode 100644 index edce446..0000000 --- a/typed/block.go +++ /dev/null @@ -1,16 +0,0 @@ -package typed - -type BlockType string - -const ( - BlockTypeParagraph BlockType = "paragraph" - BlockTypeHeading1 BlockType = "heading_1" - BlockTypeHeading2 BlockType = "heading_2" - BlockTypeHeading3 BlockType = "heading_3" - BlockTypeBulletedListItem BlockType = "bulleted_list_item" - BlockTypeNumberedListItem BlockType = "numbered_list_item" - BlockTypeToDo BlockType = "to_do" - BlockTypeToggle BlockType = "toggle" - BlockTypeChildPage BlockType = "child_page" - BlockTypeUnsupported BlockType = "unsupported" -) diff --git a/typed/color.go b/typed/color.go deleted file mode 100644 index c374020..0000000 --- a/typed/color.go +++ /dev/null @@ -1,25 +0,0 @@ -package typed - -type Color string - -const ( - DefaultColor Color = "default" - GrayColor Color = "gray" - BrownColor Color = "brown" - OrangeColor Color = "orange" - YellowColor Color = "yellow" - GreenColor Color = "green" - BlueColor Color = "blue" - PurpleColor Color = "purple" - PinkColor Color = "pink" - RedColor Color = "red" - GrayBackgroundColor Color = "gray_background" - BrownBackgroundColor Color = "brown_background" - OrangeBackgroundColor Color = "orange_background" - YellowBackgroundColor Color = "yellow_background" - GreenBackgroundColor Color = "green_background" - BlueBackgroundColor Color = "blue_background" - PurpleBackgroundColor Color = "purple_background" - PinkBackgroundColor Color = "pink_background" - RedBackgroundColor Color = "red_background" -) diff --git a/typed/format.go b/typed/format.go deleted file mode 100644 index 77106e6..0000000 --- a/typed/format.go +++ /dev/null @@ -1,17 +0,0 @@ -package typed - -type NumberFormat string - -const ( - NumberFormatNumber NumberFormat = "number" - NumberFormatNumberWithCommas NumberFormat = "number_with_commas" - NumberFormatPercent NumberFormat = "percent" - NumberFormatDollar NumberFormat = "dollar" - NumberFormatEuro NumberFormat = "euro" - NumberFormatPound NumberFormat = "pound" - NumberFormatYen NumberFormat = "yen" - NumberFormatRuble NumberFormat = "ruble" - NumberFormatRupee NumberFormat = "rupee" - NumberFormatWon NumberFormat = "won" - NumberFormatYuan NumberFormat = "yuan" -) diff --git a/typed/formula.go b/typed/formula.go deleted file mode 100644 index 46ce351..0000000 --- a/typed/formula.go +++ /dev/null @@ -1,10 +0,0 @@ -package typed - -type FormulaValueType string - -const ( - FormulaValueTypeString FormulaValueType = "string" - FormulaValueTypeNumber FormulaValueType = "number" - FormulaValueTypeBoolean FormulaValueType = "boolean" - FormulaValueTypeDate FormulaValueType = "date" -) diff --git a/typed/function.go b/typed/function.go deleted file mode 100644 index 08833ee..0000000 --- a/typed/function.go +++ /dev/null @@ -1,19 +0,0 @@ -package typed - -type RollupFunction string - -const ( - RollupFunctionCountAll RollupFunction = "count_all" - RollupFunctionCountValues RollupFunction = "count_values" - RollupFunctionCountUniqueValues RollupFunction = "count_unique_values" - RollupFunctionCountEmpty RollupFunction = "count_empty" - RollupFunctionCountNotEmpty RollupFunction = "count_not_empty" - RollupFunctionPercentEmpty RollupFunction = "percent_empty" - RollupFunctionPercentNotEmpty RollupFunction = "percent_not_empty" - RollupFunctionSum RollupFunction = "sum" - RollupFunctionAverage RollupFunction = "average" - RollupFunctionMedian RollupFunction = "median" - RollupFunctionMin RollupFunction = "min" - RollupFunctionMax RollupFunction = "max" - RollupFunctionRange RollupFunction = "range" -) diff --git a/typed/object.go b/typed/object.go deleted file mode 100644 index 98f266d..0000000 --- a/typed/object.go +++ /dev/null @@ -1,10 +0,0 @@ -package typed - -type ObjectType string - -const ( - ObjectTypeBlock ObjectType = "block" - ObjectTypePage ObjectType = "page" - ObjectTypeDatabase ObjectType = "database" - ObjectTypeList ObjectType = "list" -) diff --git a/typed/parent.go b/typed/parent.go deleted file mode 100644 index 0022f56..0000000 --- a/typed/parent.go +++ /dev/null @@ -1,9 +0,0 @@ -package typed - -type ParentType string - -const ( - ParentTypeDatabase ParentType = "database_id" - ParentTypePage ParentType = "page" - ParentTypeWorkspace ParentType = "workspace" -) diff --git a/typed/property.go b/typed/property.go deleted file mode 100644 index 8c2229b..0000000 --- a/typed/property.go +++ /dev/null @@ -1,48 +0,0 @@ -package typed - -type PropertyType string - -const ( - PropertyTypeTitle PropertyType = "title" - PropertyTypeRichText PropertyType = "rich_text" - PropertyTypeNumber PropertyType = "number" - PropertyTypeSelect PropertyType = "select" - PropertyTypeMultiSelect PropertyType = "multi_select" - PropertyTypeDate PropertyType = "date" - PropertyTypePeople PropertyType = "people" - PropertyTypeFile PropertyType = "file" - PropertyTypeCheckbox PropertyType = "checkbox" - PropertyTypeURL PropertyType = "url" - PropertyTypeEmail PropertyType = "email" - PropertyTypePhoneNumber PropertyType = "phone_number" - PropertyTypeFormula PropertyType = "formula" - PropertyTypeRelation PropertyType = "relation" - PropertyTypeRollup PropertyType = "rollup" - PropertyTypeCreatedTime PropertyType = "created_time" - PropertyTypeCreatedBy PropertyType = "created_by" - PropertyTypeLastEditedTime PropertyType = "last_edited_time" - PropertyTypeLastEditedBy PropertyType = "last_edited_by" -) - -type PropertyValueType string - -const ( - PropertyValueTypeRichText PropertyValueType = "rich_text" - PropertyValueTypeNumber PropertyValueType = "number" - PropertyValueTypeSelect PropertyValueType = "select" - PropertyValueTypeMultiSelect PropertyValueType = "multi_select" - PropertyValueTypeDate PropertyValueType = "date" - PropertyValueTypeFormula PropertyValueType = "formula" - PropertyValueTypeRollup PropertyValueType = "rollup" - PropertyValueTypeTitle PropertyValueType = "title" - PropertyValueTypePeople PropertyValueType = "people" - PropertyValueTypeFiles PropertyValueType = "files" - PropertyValueTypeCheckbox PropertyValueType = "checkbox" - PropertyValueTypeURL PropertyValueType = "url" - PropertyValueTypeEmail PropertyValueType = "email" - PropertyValueTypePhoneNumber PropertyValueType = "phone_number" - PropertyValueTypeCreatedTime PropertyValueType = "created_time" - PropertyValueTypeCreatedBy PropertyValueType = "created_by" - PropertyValueTypeLastEditedTime PropertyValueType = "last_edited_time" - PropertyValueTypeLastEditedBy PropertyValueType = "last_edited_by" -) diff --git a/typed/richtext.go b/typed/richtext.go deleted file mode 100644 index e3fbd0a..0000000 --- a/typed/richtext.go +++ /dev/null @@ -1,9 +0,0 @@ -package typed - -type RichTextType string - -const ( - RichTextTypeText RichTextType = "text" - RichTextTypeMention RichTextType = "mention" - RichTextTypeEquation RichTextType = "equation" -) diff --git a/typed/search.go b/typed/search.go deleted file mode 100644 index 6a7440d..0000000 --- a/typed/search.go +++ /dev/null @@ -1,27 +0,0 @@ -package typed - -type SearchFilterValue string - -const ( - SearchFilterValuePage SearchFilterValue = "page" - SearchFilterValueDatabase SearchFilterValue = "database" -) - -type SearchFilterProperty string - -const ( - SearchFilterPropertyObject SearchFilterProperty = "object" -) - -type SearchSortDirection string - -const ( - SearchSortDirectionAscending SearchSortDirection = "ascending" - SearchSortDirectionDescending SearchSortDirection = " descending" -) - -type SearchSortTimestamp string - -const ( - SearchSortTimestampLastEditedTime SearchSortTimestamp = "last_edited_time" -) diff --git a/typed/sort.go b/typed/sort.go deleted file mode 100644 index c30c8e7..0000000 --- a/typed/sort.go +++ /dev/null @@ -1,15 +0,0 @@ -package typed - -type SortTimestamp string - -const ( - SortTimestampByCreatedTime SortTimestamp = "created_time" - SortTimestampByLastEditedTime SortTimestamp = "last_edited_time" -) - -type SortDirection string - -const ( - SortDirectionAscending SortDirection = "ascending" - SortDirectionDescending SortDirection = "descending" -) diff --git a/typed/user.go b/typed/user.go deleted file mode 100644 index 8155358..0000000 --- a/typed/user.go +++ /dev/null @@ -1,8 +0,0 @@ -package typed - -type UserType string - -const ( - UserTypePerson UserType = "person" - UserTypeBot UserType = "bot" -) diff --git a/users.go b/users.go index a60901b..cdfb994 100644 --- a/users.go +++ b/users.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/mkfsn/notion-go/rest" - "github.com/mkfsn/notion-go/typed" ) type User interface { @@ -14,11 +13,11 @@ type User interface { } type baseUser struct { - Object string `json:"object"` - ID string `json:"id"` - Type typed.UserType `json:"type"` - Name string `json:"name"` - AvatarURL string `json:"avatar_url"` + Object string `json:"object"` + ID string `json:"id"` + Type UserType `json:"type"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` } func (b baseUser) isUser() {} @@ -138,7 +137,7 @@ type userDecoder struct { func (u *userDecoder) UnmarshalJSON(data []byte) error { var decoder struct { - Type typed.UserType `json:"type"` + Type UserType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { @@ -146,10 +145,10 @@ func (u *userDecoder) UnmarshalJSON(data []byte) error { } switch decoder.Type { - case typed.UserTypePerson: + case UserTypePerson: u.User = &PersonUser{} - case typed.UserTypeBot: + case UserTypeBot: u.User = &BotUser{} } From db6c2fed36e2db03acf83a2a3514f63931ec3a23 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 17:50:11 +0800 Subject: [PATCH 08/32] fix: linter --- .golangci.yml | 6 ++++++ blocks.go | 19 ++++++++++++------- databases.go | 20 +++++++++++++------- pages.go | 27 ++++++++++++++++----------- rest/client.go | 23 +++++++++++++++++------ search.go | 10 ++++++---- users.go | 13 ++++++++----- 7 files changed, 78 insertions(+), 40 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d5741f6..174b5fd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,6 +14,9 @@ linters: - godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false] - gofumpt + - exhaustivestruct + - maligned + - interfacer linters-settings: govet: @@ -22,3 +25,6 @@ linters-settings: check-shadowing: false lll: line-length: 150 + funlen: + lines: 70 + statements: 50 diff --git a/blocks.go b/blocks.go index b45a3ff..3b3e3c2 100644 --- a/blocks.go +++ b/blocks.go @@ -3,6 +3,7 @@ package notion import ( "context" "encoding/json" + "fmt" "strings" "time" @@ -43,7 +44,7 @@ func (h *HeadingBlock) UnmarshalJSON(data []byte) error { var text []richTextDecoder if err := json.Unmarshal(data, &text); err != nil { - return nil + return fmt.Errorf("failed to unmarshal HeadingBlock: %w", err) } h.Text = make([]RichText, 0, len(text)) @@ -82,7 +83,7 @@ func (r *RichTextBlock) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return nil + return fmt.Errorf("failed to unmarshal RichTextBlock: %w", err) } r.Text = make([]RichText, 0, len(r.Text)) @@ -184,7 +185,7 @@ func (b *BlocksChildrenListResponse) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal BlocksChildrenListResponse: %w", err) } b.Results = make([]Block, 0, len(alias.Results)) @@ -211,7 +212,7 @@ func (b *BlocksChildrenAppendResponse) UnmarshalJSON(data []byte) error { var decoder blockDecoder if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal BlocksChildrenAppendResponse: %w", err) } b.Block = decoder.Block @@ -236,6 +237,7 @@ func newBlocksChildrenClient(restClient rest.Interface) *blocksChildrenClient { func (b *blocksChildrenClient) List(ctx context.Context, params BlocksChildrenListParameters) (*BlocksChildrenListResponse, error) { var result BlocksChildrenListResponse + var failure HTTPError err := b.restClient.New().Get(). @@ -244,11 +246,12 @@ func (b *blocksChildrenClient) List(ctx context.Context, params BlocksChildrenLi BodyJSON(nil). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } func (b *blocksChildrenClient) Append(ctx context.Context, params BlocksChildrenAppendParameters) (*BlocksChildrenAppendResponse, error) { var result BlocksChildrenAppendResponse + var failure HTTPError err := b.restClient.New().Patch(). @@ -257,20 +260,22 @@ func (b *blocksChildrenClient) Append(ctx context.Context, params BlocksChildren BodyJSON(params). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } type blockDecoder struct { Block } +// UnmarshalJSON implements json.Unmarshaler +// nolint: cyclop func (b *blockDecoder) UnmarshalJSON(data []byte) error { var decoder struct { Type BlockType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal Block: %w", err) } switch decoder.Type { diff --git a/databases.go b/databases.go index 19d3e9c..6a12d3f 100644 --- a/databases.go +++ b/databases.go @@ -3,6 +3,7 @@ package notion import ( "context" "encoding/json" + "fmt" "strings" "time" @@ -33,7 +34,7 @@ func (d *Database) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal Database: %w", err) } d.Title = make([]RichText, 0, len(alias.Title)) @@ -521,6 +522,7 @@ func newDatabasesClient(restClient rest.Interface) *databasesClient { func (d *databasesClient) Retrieve(ctx context.Context, params DatabasesRetrieveParameters) (*DatabasesRetrieveResponse, error) { var result DatabasesRetrieveResponse + var failure HTTPError err := d.restClient.New().Get(). @@ -529,11 +531,12 @@ func (d *databasesClient) Retrieve(ctx context.Context, params DatabasesRetrieve BodyJSON(nil). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } func (d *databasesClient) List(ctx context.Context, params DatabasesListParameters) (*DatabasesListResponse, error) { var result DatabasesListResponse + var failure HTTPError err := d.restClient.New().Get(). @@ -542,11 +545,12 @@ func (d *databasesClient) List(ctx context.Context, params DatabasesListParamete BodyJSON(params). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } func (d *databasesClient) Query(ctx context.Context, params DatabasesQueryParameters) (*DatabasesQueryResponse, error) { var result DatabasesQueryResponse + var failure HTTPError err := d.restClient.New().Post(). @@ -555,7 +559,7 @@ func (d *databasesClient) Query(ctx context.Context, params DatabasesQueryParame BodyJSON(params). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } type richTextDecoder struct { @@ -568,7 +572,7 @@ func (r *richTextDecoder) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal RichText: %w", err) } switch decoder.Type { @@ -589,13 +593,15 @@ type propertyDecoder struct { Property } -func (p *propertyDecoder) Unmarshal(data []byte) error { +// UnmarshalJSON implements json.Unmarshaler +// nolint: cyclop +func (p *propertyDecoder) UnmarshalJSON(data []byte) error { var decoder struct { Type PropertyType `json:"type"` } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal Property: %w", err) } switch decoder.Type { diff --git a/pages.go b/pages.go index 856d681..3ef4495 100644 --- a/pages.go +++ b/pages.go @@ -3,6 +3,7 @@ package notion import ( "context" "encoding/json" + "fmt" "strings" "time" @@ -80,7 +81,7 @@ func (p *Page) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal Page: %w", err) } p.Parent = alias.Parent.Parent @@ -127,7 +128,7 @@ func (t *TitlePropertyValue) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal TitlePropertyValue: %w", err) } t.Title = make([]RichText, 0, len(alias.Title)) @@ -155,7 +156,7 @@ func (r *RichTextPropertyValue) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal RichTextPropertyValue: %w", err) } r.RichText = make([]RichText, 0, len(alias.RichText)) @@ -248,7 +249,7 @@ func (f *FormulaPropertyValue) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal FormulaPropertyValue: %w", err) } f.Formula = alias.Formula.FormulaValue @@ -386,6 +387,7 @@ func newPagesClient(restClient rest.Interface) *pagesClient { func (p *pagesClient) Retrieve(ctx context.Context, params PagesRetrieveParameters) (*PagesRetrieveResponse, error) { var result PagesRetrieveResponse + var failure HTTPError err := p.restClient.New().Get(). @@ -394,11 +396,12 @@ func (p *pagesClient) Retrieve(ctx context.Context, params PagesRetrieveParamete BodyJSON(nil). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } func (p *pagesClient) Update(ctx context.Context, params PagesUpdateParameters) (*PagesUpdateResponse, error) { var result PagesUpdateResponse + var failure HTTPError err := p.restClient.New().Patch(). @@ -407,11 +410,12 @@ func (p *pagesClient) Update(ctx context.Context, params PagesUpdateParameters) BodyJSON(params). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } func (p *pagesClient) Create(ctx context.Context, params PagesCreateParameters) (*PagesCreateResponse, error) { var result PagesCreateResponse + var failure HTTPError err := p.restClient.New().Post(). @@ -420,7 +424,7 @@ func (p *pagesClient) Create(ctx context.Context, params PagesCreateParameters) BodyJSON(params). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } type formulaValueDecoder struct { @@ -433,7 +437,7 @@ func (f *formulaValueDecoder) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal FormulaValue: %w", err) } switch decoder.Type { @@ -463,7 +467,7 @@ func (p *parentDecoder) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal Parent: %w", err) } switch decoder.Type { @@ -484,13 +488,15 @@ type propertyValueDecoder struct { PropertyValue } +// UnmarshalJSON implements json.Unmarshaler +// nolint: cyclop func (p *propertyValueDecoder) UnmarshalJSON(data []byte) error { var decoder struct { Type PropertyValueType `json:"type,omitempty"` } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal PropertyValue: %w", err) } switch decoder.Type { @@ -547,7 +553,6 @@ func (p *propertyValueDecoder) UnmarshalJSON(data []byte) error { case PropertyValueTypeLastEditedBy: p.PropertyValue = &LastEditedByPropertyValue{} - } return json.Unmarshal(data, p.PropertyValue) diff --git a/rest/client.go b/rest/client.go index e384b12..cdb3504 100644 --- a/rest/client.go +++ b/rest/client.go @@ -35,56 +35,67 @@ func (r *restClient) New() Interface { header: r.header.Clone(), httpClient: r.httpClient, // TODO: deep copy } + return newRestClient } func (r *restClient) BearerToken(token string) Interface { r.header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + return r } func (r *restClient) BaseURL(baseURL string) Interface { r.baseURL = baseURL + return r } func (r *restClient) Client(httpClient *http.Client) Interface { r.httpClient = httpClient + return r } func (r *restClient) UserAgent(userAgent string) Interface { r.header.Set("User-Agent", userAgent) + return r } func (r *restClient) Header(key, value string) Interface { r.header.Set(key, value) + return r } func (r *restClient) Get() Interface { r.method = http.MethodGet + return r } func (r *restClient) Post() Interface { r.method = http.MethodPost + return r } func (r *restClient) Patch() Interface { r.method = http.MethodPatch + return r } func (r *restClient) Endpoint(endpoint string) Interface { r.endpoint = endpoint + return r } func (r *restClient) QueryStruct(queryStruct interface{}) Interface { r.queryStruct = queryStruct + return r } @@ -101,17 +112,17 @@ func (r *restClient) BodyJSON(bodyJSON interface{}) Interface { func (r *restClient) Request(ctx context.Context) (*http.Request, error) { v, err := query.Values(r.queryStruct) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to build query parameters: %w", err) } b, err := json.Marshal(r.bodyJSON) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to marshal body to JSON: %w", err) } req, err := http.NewRequestWithContext(ctx, r.method, r.baseURL+r.endpoint, bytes.NewBuffer(b)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create an HTTP request: %w", err) } req.URL.RawQuery = v.Encode() @@ -129,13 +140,13 @@ func (r *restClient) Receive(ctx context.Context, success, failure interface{}) resp, err := r.httpClient.Do(req) if err != nil { - return err + return fmt.Errorf("failed to process an HTTP request: %w", err) } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { - return err + return fmt.Errorf("failed to read data from response body: %w", err) } return r.decodeResponseData(resp.StatusCode, b, success, failure) @@ -155,7 +166,7 @@ func (r *restClient) decodeResponseData(statusCode int, data []byte, success, fa } if err := json.Unmarshal(data, failure); err != nil { - return err + return fmt.Errorf("failed to unmarshal error message from HTTP response body: %w", err) } return failure.(error) diff --git a/search.go b/search.go index b242ff9..d563670 100644 --- a/search.go +++ b/search.go @@ -3,6 +3,7 @@ package notion import ( "context" "encoding/json" + "fmt" "github.com/mkfsn/notion-go/rest" ) @@ -52,7 +53,7 @@ func (s *SearchResponse) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal SearchResponse: %w", err) } s.Results = make([]SearchableObject, 0, len(alias.Results)) @@ -80,6 +81,7 @@ func newSearchClient(restClient rest.Interface) *searchClient { func (s *searchClient) Search(ctx context.Context, params SearchParameters) (*SearchResponse, error) { var result SearchResponse + var failure HTTPError err := s.restClient.New(). @@ -89,7 +91,7 @@ func (s *searchClient) Search(ctx context.Context, params SearchParameters) (*Se BodyJSON(params). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } type searchableObjectDecoder struct { @@ -102,7 +104,7 @@ func (s *searchableObjectDecoder) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal SearchableObject: %w", err) } switch decoder.Object { @@ -112,7 +114,7 @@ func (s *searchableObjectDecoder) UnmarshalJSON(data []byte) error { case ObjectTypeDatabase: s.SearchableObject = &Database{} - case ObjectTypeBlock: + case ObjectTypeBlock, ObjectTypeList: return ErrUnknown } diff --git a/users.go b/users.go index cdfb994..56889a6 100644 --- a/users.go +++ b/users.go @@ -3,6 +3,7 @@ package notion import ( "context" "encoding/json" + "fmt" "strings" "github.com/mkfsn/notion-go/rest" @@ -50,7 +51,7 @@ func (u *UsersRetrieveResponse) UnmarshalJSON(data []byte) (err error) { var decoder userDecoder if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal UsersRetrieveResponse: %w", err) } u.User = decoder.User @@ -78,7 +79,7 @@ func (u *UsersListResponse) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &alias); err != nil { - return err + return fmt.Errorf("failed to unmarshal UsersListResponse: %w", err) } u.Results = make([]User, 0, len(alias.Results)) @@ -107,6 +108,7 @@ func newUsersClient(restClient rest.Interface) *usersClient { func (u *usersClient) Retrieve(ctx context.Context, params UsersRetrieveParameters) (*UsersRetrieveResponse, error) { var result UsersRetrieveResponse + var failure HTTPError err := u.restClient.New().Get(). @@ -115,11 +117,12 @@ func (u *usersClient) Retrieve(ctx context.Context, params UsersRetrieveParamete BodyJSON(nil). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } func (u *usersClient) List(ctx context.Context, params UsersListParameters) (*UsersListResponse, error) { var result UsersListResponse + var failure HTTPError err := u.restClient.New().Get(). @@ -128,7 +131,7 @@ func (u *usersClient) List(ctx context.Context, params UsersListParameters) (*Us BodyJSON(params). Receive(ctx, &result, &failure) - return &result, err + return &result, err // nolint:wrapcheck } type userDecoder struct { @@ -141,7 +144,7 @@ func (u *userDecoder) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &decoder); err != nil { - return err + return fmt.Errorf("failed to unmarshal User: %w", err) } switch decoder.Type { From 757f0e90dbb504168505c47a799696d17f2532ca Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 17:58:59 +0800 Subject: [PATCH 09/32] doc: add usage code --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 80c5fab..722cbae 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,40 @@ go get -u github.com/mkfsn/notion-go ## Usage ```go +c := notion.New("") +// Retrieve block children +c.Blocks().List(...) + +// Append block children +c.Blocks().Append(...) + +// List databases +c.Databases().List(...) + +// Query a database +c.Databases().Query(...) + +// Retrieve a database +c.Databases().Retrieve(...) + +// Create a page +c.Pages().Create(...) + +// Retrieve a page +c.Pages().Retreive(...) + +// Update page properties +c.Pages().Update(...) + +// List all users +c.Users().List(...) + +// Retrieve a users +c.Users().Retrieve(...) + +// Search +c.Search(...) ``` ## Supported Features From c13ed76657a32e1e2f96fb6a8079eb9a6b6f361a Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 18:00:41 +0800 Subject: [PATCH 10/32] doc: add examples link --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 722cbae..e324814 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ c.Users().Retrieve(...) c.Search(...) ``` +For more information, please see [examples](./examples). + ## Supported Features This client supports all endpoints in the [Notion API](https://developers.notion.com/reference/intro). From 7422f134b109cd22941ab1e1b028df71a727c630 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 18:01:48 +0800 Subject: [PATCH 11/32] ci: fix error: no such linter "exhaustivestruct" --- .golangci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 174b5fd..a5420c1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,7 +14,6 @@ linters: - godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false] - gofumpt - - exhaustivestruct - maligned - interfacer From 32a50179ebb1c69802a46a24c37af47aeeb5c159 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 18:10:14 +0800 Subject: [PATCH 12/32] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..18c9147 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From 58fa095ba8d00282c66ef09f1c31a740e50f69e0 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 23:22:42 +0800 Subject: [PATCH 13/32] test: add users list/retrive test --- consts.go | 1 + go.mod | 5 +- go.sum | 11 +++ search.go | 2 +- users.go | 13 +-- users_test.go | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 288 insertions(+), 10 deletions(-) create mode 100644 users_test.go diff --git a/consts.go b/consts.go index e3611a1..46c3bf3 100644 --- a/consts.go +++ b/consts.go @@ -89,6 +89,7 @@ const ( ObjectTypePage ObjectType = "page" ObjectTypeDatabase ObjectType = "database" ObjectTypeList ObjectType = "list" + ObjectTypeUser ObjectType = "user" ) type ParentType string diff --git a/go.mod b/go.mod index 3d695d7..0a67466 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/mkfsn/notion-go go 1.16 -require github.com/google/go-querystring v1.1.0 +require ( + github.com/google/go-querystring v1.1.0 + github.com/stretchr/testify v1.7.0 +) diff --git a/go.sum b/go.sum index f99081b..b6ff92c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,16 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/search.go b/search.go index d563670..dbb85b0 100644 --- a/search.go +++ b/search.go @@ -114,7 +114,7 @@ func (s *searchableObjectDecoder) UnmarshalJSON(data []byte) error { case ObjectTypeDatabase: s.SearchableObject = &Database{} - case ObjectTypeBlock, ObjectTypeList: + case ObjectTypeBlock, ObjectTypeList, ObjectTypeUser: return ErrUnknown } diff --git a/users.go b/users.go index 56889a6..092729c 100644 --- a/users.go +++ b/users.go @@ -14,11 +14,11 @@ type User interface { } type baseUser struct { - Object string `json:"object"` - ID string `json:"id"` - Type UserType `json:"type"` - Name string `json:"name"` - AvatarURL string `json:"avatar_url"` + Object ObjectType `json:"object"` + ID string `json:"id"` + Type UserType `json:"type"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` } func (b baseUser) isUser() {} @@ -113,8 +113,6 @@ func (u *usersClient) Retrieve(ctx context.Context, params UsersRetrieveParamete err := u.restClient.New().Get(). Endpoint(strings.Replace(APIUsersRetrieveEndpoint, "{user_id}", params.UserID, 1)). - QueryStruct(params). - BodyJSON(nil). Receive(ctx, &result, &failure) return &result, err // nolint:wrapcheck @@ -128,7 +126,6 @@ func (u *usersClient) List(ctx context.Context, params UsersListParameters) (*Us err := u.restClient.New().Get(). Endpoint(APIUsersListEndpoint). QueryStruct(params). - BodyJSON(params). Receive(ctx, &result, &failure) return &result, err // nolint:wrapcheck diff --git a/users_test.go b/users_test.go new file mode 100644 index 0000000..10b691a --- /dev/null +++ b/users_test.go @@ -0,0 +1,266 @@ +package notion + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/mkfsn/notion-go/rest" + "github.com/stretchr/testify/assert" +) + +func Test_usersClient_Retrieve(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params UsersRetrieveParameters + } + + type wants struct { + response *UsersRetrieveResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Bot User", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodGet, request.Method) + assert.Equal(t, "/v1/users/9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "object": "user", + "id": "9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57", + "type": "bot", + "bot": {}, + "name": "Doug Engelbot", + "avatar_url": "https://secure.notion-static.com/6720d746-3402-4171-8ebb-28d15144923c.jpg" + }`, + )) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: UsersRetrieveParameters{UserID: "9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57"}, + }, + wants: wants{ + response: &UsersRetrieveResponse{ + User: &BotUser{ + baseUser: baseUser{ + Object: ObjectTypeUser, + ID: "9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57", + Type: UserTypeBot, + Name: "Doug Engelbot", + AvatarURL: "https://secure.notion-static.com/6720d746-3402-4171-8ebb-28d15144923c.jpg", + }, + Bot: Bot{}, + }, + }, + }, + }, + + { + name: "Person User", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodGet, request.Method) + assert.Equal(t, "/v1/users/d40e767c-d7af-4b18-a86d-55c61f1e39a4", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "object": "user", + "id": "d40e767c-d7af-4b18-a86d-55c61f1e39a4", + "type": "person", + "person": { + "email": "avo@example.org" + }, + "name": "Avocado Lovelace", + "avatar_url": "https://secure.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg" + }`, + )) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: UsersRetrieveParameters{UserID: "d40e767c-d7af-4b18-a86d-55c61f1e39a4"}, + }, + wants: wants{ + response: &UsersRetrieveResponse{ + User: &PersonUser{ + baseUser: baseUser{ + Object: ObjectTypeUser, + ID: "d40e767c-d7af-4b18-a86d-55c61f1e39a4", + Type: UserTypePerson, + Name: "Avocado Lovelace", + AvatarURL: "https://secure.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg", + }, + Person: Person{Email: "avo@example.org"}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + + sut := &usersClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + + got, err := sut.Retrieve(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func Test_usersClient_List(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params UsersListParameters + } + + type wants struct { + response *UsersListResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "List two users in one page", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodGet, request.Method) + assert.Equal(t, "/v1/users?page_size=2", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "results": [ + { + "object": "user", + "id": "d40e767c-d7af-4b18-a86d-55c61f1e39a4", + "type": "person", + "person": { + "email": "avo@example.org" + }, + "name": "Avocado Lovelace", + "avatar_url": "https://secure.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg" + }, + { + "object": "user", + "id": "9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57", + "type": "bot", + "bot": {}, + "name": "Doug Engelbot", + "avatar_url": "https://secure.notion-static.com/6720d746-3402-4171-8ebb-28d15144923c.jpg" + } + ], + "next_cursor": "fe2cc560-036c-44cd-90e8-294d5a74cebc", + "has_more": true + }`, + )) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: UsersListParameters{ + PaginationParameters: PaginationParameters{ + StartCursor: "", + PageSize: 2, + }, + }, + }, + wants: wants{ + response: &UsersListResponse{ + PaginatedList: PaginatedList{ + NextCursor: "fe2cc560-036c-44cd-90e8-294d5a74cebc", + HasMore: true, + }, + Results: []User{ + &PersonUser{ + baseUser: baseUser{ + Object: ObjectTypeUser, + ID: "d40e767c-d7af-4b18-a86d-55c61f1e39a4", + Type: UserTypePerson, + Name: "Avocado Lovelace", + AvatarURL: "https://secure.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg", + }, + Person: Person{Email: "avo@example.org"}, + }, + &BotUser{ + baseUser: baseUser{ + Object: ObjectTypeUser, + ID: "9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57", + Type: UserTypeBot, + Name: "Doug Engelbot", + AvatarURL: "https://secure.notion-static.com/6720d746-3402-4171-8ebb-28d15144923c.jpg", + }, + Bot: Bot{}, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + + sut := &usersClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + + got, err := sut.List(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} From cb5682e1ca7cc5fce5107c15f85cc9a6418ec343 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 23:36:06 +0800 Subject: [PATCH 14/32] ci: Upload coverage report to codecov (#3) --- .github/workflows/develop.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/develop.yaml b/.github/workflows/develop.yaml index ba6256e..fc2a03d 100644 --- a/.github/workflows/develop.yaml +++ b/.github/workflows/develop.yaml @@ -28,8 +28,11 @@ jobs: - uses: actions/checkout@v2.3.3 - - name: Tesing - run: go test -v ./... + - name: Tesing with coverage + run: go test -race -coverprofile=coverage.txt -covermode=atomic + + - name: Upload coverage report + uses: codecov/codecov-action@v1 - name: Build binary run: go build -v From 53abb56b343d6aecc5f7ae35e375e3f3ac442fac Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Mon, 17 May 2021 23:50:37 +0800 Subject: [PATCH 15/32] doc: add build status and coverage --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e324814..84d32d8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # notion-go +[![Actions Status](https://github.com/mkfsn/notion-go/actions/workflows/develop.yaml/badge.svg)](https://github.com/mkfsn/notion-go/actions) +[![codecov](https://codecov.io/gh/mkfsn/notion-go/branch/develop/graph/badge.svg?token=NA64P6EPQ0)](https://codecov.io/gh/mkfsn/notion-go) + + A go client for the [Notion API](https://developers.notion.com/) ## Description From 26a801fa102ceaf28f10bce8886c1d4aea7386aa Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Tue, 18 May 2021 00:19:49 +0800 Subject: [PATCH 16/32] test: add search test (#4) --- search_test.go | 269 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 search_test.go diff --git a/search_test.go b/search_test.go new file mode 100644 index 0000000..56a2248 --- /dev/null +++ b/search_test.go @@ -0,0 +1,269 @@ +package notion + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/mkfsn/notion-go/rest" + "github.com/stretchr/testify/assert" +) + +func Test_searchClient_Search(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params SearchParameters + } + + type wants struct { + response *SearchResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Search two objects in one page", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodPost, request.Method) + assert.Equal(t, "/v1/search?page_size=2", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "has_more": false, + "next_cursor": null, + "object": "list", + "results": [ + { + "created_time": "2021-04-22T22:23:26.080Z", + "id": "e6c6f8ff-c70e-4970-91ba-98f03e0d7fc6", + "last_edited_time": "2021-04-23T04:21:00.000Z", + "object": "database", + "properties": { + "Name": { + "id": "title", + "title": {}, + "type": "title" + }, + "Task Type": { + "id": "vd@l", + "multi_select": { + "options": [] + }, + "type": "multi_select" + } + }, + "title": [ + { + "annotations": { + "bold": false, + "code": false, + "color": "default", + "italic": false, + "strikethrough": false, + "underline": false + }, + "href": null, + "plain_text": "Tasks", + "text": { + "content": "Tasks", + "link": null + }, + "type": "text" + } + ] + }, + { + "archived": false, + "created_time": "2021-04-23T04:21:00.000Z", + "id": "4f555b50-3a9b-49cb-924c-3746f4ca5522", + "last_edited_time": "2021-04-23T04:21:00.000Z", + "object": "page", + "parent": { + "database_id": "e6c6f8ff-c70e-4970-91ba-98f03e0d7fc6", + "type": "database_id" + }, + "properties": { + "Name": { + "id": "title", + "title": [ + { + "annotations": { + "bold": false, + "code": false, + "color": "default", + "italic": false, + "strikethrough": false, + "underline": false + }, + "href": null, + "plain_text": "Task 1", + "text": { + "content": "Task1 1", + "link": null + }, + "type": "text" + } + ], + "type": "title" + } + } + } + ] + }`, + )) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: SearchParameters{ + PaginationParameters: PaginationParameters{ + StartCursor: "", + PageSize: 2, + }, + Query: "External tasks", + Sort: SearchSort{ + Direction: SearchSortDirectionAscending, + Timestamp: SearchSortTimestampLastEditedTime, + }, + Filter: SearchFilter{}, + }, + }, + wants: wants{ + response: &SearchResponse{ + PaginatedList: PaginatedList{ + Object: ObjectTypeList, + HasMore: false, + NextCursor: "", + }, + Results: []SearchableObject{ + &Database{ + Object: ObjectTypeDatabase, + ID: "e6c6f8ff-c70e-4970-91ba-98f03e0d7fc6", + CreatedTime: time.Date(2021, 4, 22, 22, 23, 26, 80_000_000, time.UTC), + LastEditedTime: time.Date(2021, 4, 23, 4, 21, 0, 0, time.UTC), + Title: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Tasks", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: DefaultColor, + }, + }, + Text: TextObject{ + Content: "Tasks", + Link: nil, + }, + }, + }, + Properties: map[string]Property{ + "Name": &TitleProperty{ + baseProperty: baseProperty{ + ID: "title", + Type: PropertyTypeTitle, + }, + Title: map[string]interface{}{}, + }, + "Task Type": &MultiSelectProperty{ + baseProperty: baseProperty{ + ID: "vd@l", + Type: PropertyTypeMultiSelect, + }, + MultiSelect: MultiSelectPropertyOption{ + Options: []MultiSelectOption{}, + }, + }, + }, + }, + &Page{ + Object: ObjectTypePage, + ID: "4f555b50-3a9b-49cb-924c-3746f4ca5522", + Parent: &DatabaseParent{ + baseParent: baseParent{ + Type: ParentTypeDatabase, + }, + DatabaseID: "e6c6f8ff-c70e-4970-91ba-98f03e0d7fc6", + }, + Properties: map[string]PropertyValue{ + "Name": &TitlePropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "title", + Type: PropertyValueTypeTitle, + }, + Title: []RichText{ + + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Task 1", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: DefaultColor, + }, + }, + Text: TextObject{ + Content: "Task1 1", + Link: nil, + }, + }, + }, + }, + }, + CreatedTime: time.Date(2021, 4, 23, 4, 21, 0, 0, time.UTC), + LastEditedTime: time.Date(2021, 4, 23, 4, 21, 0, 0, time.UTC), + Archived: false, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + + sut := &searchClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + + got, err := sut.Search(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} From c0badc3fd48de607784c55bf73f3724d1a0c25b2 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Tue, 18 May 2021 09:33:10 +0800 Subject: [PATCH 17/32] doc: add go report badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 84d32d8..454b1b5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # notion-go +[![Go Report Card](https://goreportcard.com/badge/github.com/mkfsn/notion-go)](https://goreportcard.com/report/github.com/mkfsn/notion-go) [![Actions Status](https://github.com/mkfsn/notion-go/actions/workflows/develop.yaml/badge.svg)](https://github.com/mkfsn/notion-go/actions) [![codecov](https://codecov.io/gh/mkfsn/notion-go/branch/develop/graph/badge.svg?token=NA64P6EPQ0)](https://codecov.io/gh/mkfsn/notion-go) From 5e3e0affe83b7b22673e145f2b72e4b12d73959c Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Tue, 18 May 2021 09:33:38 +0800 Subject: [PATCH 18/32] doc: fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 454b1b5..7cd6658 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ c.Pages().Update(...) // List all users c.Users().List(...) -// Retrieve a users +// Retrieve a user c.Users().Retrieve(...) // Search From 2e3807ba77773579d91da31b5fc795d21d18a0d6 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Tue, 18 May 2021 22:57:54 +0800 Subject: [PATCH 19/32] test: add databases list/retrieve/query test (#5) --- consts.go | 39 +-- databases.go | 67 ++-- databases_test.go | 817 ++++++++++++++++++++++++++++++++++++++++++++++ pages.go | 30 +- search_test.go | 4 +- 5 files changed, 896 insertions(+), 61 deletions(-) create mode 100644 databases_test.go diff --git a/consts.go b/consts.go index 46c3bf3..f79eaa8 100644 --- a/consts.go +++ b/consts.go @@ -18,25 +18,25 @@ const ( type Color string const ( - DefaultColor Color = "default" - GrayColor Color = "gray" - BrownColor Color = "brown" - OrangeColor Color = "orange" - YellowColor Color = "yellow" - GreenColor Color = "green" - BlueColor Color = "blue" - PurpleColor Color = "purple" - PinkColor Color = "pink" - RedColor Color = "red" - GrayBackgroundColor Color = "gray_background" - BrownBackgroundColor Color = "brown_background" - OrangeBackgroundColor Color = "orange_background" - YellowBackgroundColor Color = "yellow_background" - GreenBackgroundColor Color = "green_background" - BlueBackgroundColor Color = "blue_background" - PurpleBackgroundColor Color = "purple_background" - PinkBackgroundColor Color = "pink_background" - RedBackgroundColor Color = "red_background" + ColorDefault Color = "default" + ColorGray Color = "gray" + ColorBrown Color = "brown" + ColorOrange Color = "orange" + ColorYellow Color = "yellow" + ColorGreen Color = "green" + ColorBlue Color = "blue" + ColorPurple Color = "purple" + ColorPink Color = "pink" + ColorRed Color = "red" + BackgroundColorGray Color = "gray_background" + BackgroundColorBrown Color = "brown_background" + BackgroundColorOrange Color = "orange_background" + BackgroundColorYellow Color = "yellow_background" + BackgroundColorGreen Color = "green_background" + BackgroundColorBlue Color = "blue_background" + BackgroundColorPurple Color = "purple_background" + BackgroundColorPink Color = "pink_background" + BackgroundColorRed Color = "red_background" ) type NumberFormat string @@ -133,6 +133,7 @@ const ( PropertyValueTypeMultiSelect PropertyValueType = "multi_select" PropertyValueTypeDate PropertyValueType = "date" PropertyValueTypeFormula PropertyValueType = "formula" + PropertyValueTypeRelation PropertyValueType = "relation" PropertyValueTypeRollup PropertyValueType = "rollup" PropertyValueTypeTitle PropertyValueType = "title" PropertyValueTypePeople PropertyValueType = "people" diff --git a/databases.go b/databases.go index 6a12d3f..e897f79 100644 --- a/databases.go +++ b/databases.go @@ -183,14 +183,14 @@ type NumberProperty struct { } type SelectOption struct { - Name string `json:"name"` ID string `json:"id"` + Name string `json:"name"` Color Color `json:"color"` } type MultiSelectOption struct { - Name string `json:"name"` ID string `json:"id"` + Name string `json:"name"` Color Color `json:"color"` } @@ -247,20 +247,24 @@ type PhoneNumberProperty struct { PhoneNumber interface{} `json:"phone_number"` } +type Formula struct { + Expression string `json:"expression"` +} + type FormulaProperty struct { baseProperty - Formula struct { - Expression string `json:"expression"` - } `json:"formula"` + Formula Formula `json:"formula"` +} + +type Relation struct { + DatabaseID string `json:"database_id"` + SyncedPropertyName *string `json:"synced_property_name"` + SyncedPropertyID *string `json:"synced_property_id"` } type RelationProperty struct { baseProperty - Relation struct { - DatabaseID string `json:"database_id"` - SyncedPropertyName *string `json:"synced_property_name"` - SyncedPropertyID *string `json:"synced_property_id"` - } `json:"relation"` + Relation Relation `json:"relation"` } type RollupPropertyOption struct { @@ -297,7 +301,7 @@ type LastEditedByProperty struct { } type DatabasesRetrieveParameters struct { - DatabaseID string `json:"-"` + DatabaseID string `json:"-" url:"-"` } type DatabasesRetrieveResponse struct { @@ -357,8 +361,8 @@ type NumberFilter struct { LessThan *float64 `json:"less_than,omitempty"` GreaterThanOrEqualTo *float64 `json:"greater_than_or_equal_to,omitempty"` LessThanOrEqualTo *float64 `json:"less_than_or_equal_to,omitempty"` - IsEmpty *bool `json:"is_empty,omitempty"` - IsNotEmpty *bool `json:"is_not_empty,omitempty"` + IsEmpty bool `json:"is_empty,omitempty"` + IsNotEmpty bool `json:"is_not_empty,omitempty"` } // SingleNumberFilter is a number filter condition applies to database properties of type "number". @@ -368,8 +372,8 @@ type SingleNumberFilter struct { } type CheckboxFilter struct { - Equals *bool `json:"equals,omitempty"` - DoesNotEqual *bool `json:"does_not_equal,omitempty"` + Equals bool `json:"equals,omitempty"` + DoesNotEqual bool `json:"does_not_equal,omitempty"` } // SingleCheckboxFilter is a checkbox filter condition applies to database properties of type "checkbox". @@ -381,8 +385,8 @@ type SingleCheckboxFilter struct { type SelectFilter struct { Equals *string `json:"equals,omitempty"` DoesNotEqual *string `json:"does_not_equal,omitempty"` - IsEmpty *bool `json:"is_empty,omitempty"` - IsNotEmpty *bool `json:"is_not_empty,omitempty"` + IsEmpty bool `json:"is_empty,omitempty"` + IsNotEmpty bool `json:"is_not_empty,omitempty"` } // SingleSelectFilter is a select filter condition applies to database properties of type "select". @@ -394,8 +398,8 @@ type SingleSelectFilter struct { type MultiSelectFilter struct { Contains *string `json:"contains,omitempty"` DoesNotContain *string `json:"does_not_contain,omitempty"` - IsEmpty *bool `json:"is_empty,omitempty"` - IsNotEmpty *bool `json:"is_not_empty,omitempty"` + IsEmpty bool `json:"is_empty,omitempty"` + IsNotEmpty bool `json:"is_not_empty,omitempty"` } // SingleMultiSelectFilter is a multi-select filter condition applies to database properties of type "multi_select". @@ -409,8 +413,8 @@ type DateFilter struct { Before *string `json:"before,omitempty"` After *string `json:"after,omitempty"` OnOrBefore *string `json:"on_or_before,omitempty"` - IsEmpty *bool `json:"is_empty,omitempty"` - IsNotEmpty *bool `json:"is_not_empty,omitempty"` + IsEmpty bool `json:"is_empty,omitempty"` + IsNotEmpty bool `json:"is_not_empty,omitempty"` OnOrAfter *string `json:"on_or_after,omitempty"` PastWeek map[string]interface{} `json:"past_week,omitempty"` PastMonth map[string]interface{} `json:"past_month,omitempty"` @@ -431,8 +435,8 @@ type SingleDateFilter struct { type PeopleFilter struct { Contains *string `json:"contains,omitempty"` DoesNotContain *string `json:"does_not_contain,omitempty"` - IsEmpty *bool `json:"is_empty,omitempty"` - IsNotEmpty *bool `json:"is_not_empty,omitempty"` + IsEmpty bool `json:"is_empty,omitempty"` + IsNotEmpty bool `json:"is_not_empty,omitempty"` } // SinglePeopleFilter is a people filter condition applies to database properties of types "people", "created_by", and "last_edited_by". @@ -444,8 +448,8 @@ type SinglePeopleFilter struct { } type FilesFilter struct { - IsEmpty *bool `json:"is_empty,omitempty"` - IsNotEmpty *bool `json:"is_not_empty,omitempty"` + IsEmpty bool `json:"is_empty,omitempty"` + IsNotEmpty bool `json:"is_not_empty,omitempty"` } // SingleFilesFilter is a files filter condition applies to database properties of type "files". @@ -457,8 +461,8 @@ type SingleFilesFilter struct { type RelationFilter struct { Contains *string `json:"contains,omitempty"` DoesNotContain *string `json:"does_not_contain,omitempty"` - IsEmpty *bool `json:"is_empty,omitempty"` - IsNotEmpty *bool `json:"is_not_empty,omitempty"` + IsEmpty bool `json:"is_empty,omitempty"` + IsNotEmpty bool `json:"is_not_empty,omitempty"` } // SingleRelationFilter is a relation filter condition applies to database properties of type "relation". @@ -490,13 +494,13 @@ func (c CompoundFilter) isFilter() {} type DatabasesQueryParameters struct { PaginationParameters // Identifier for a Notion database. - DatabaseID string `json:"-"` + DatabaseID string `json:"-" url:"-"` // When supplied, limits which pages are returned based on the // [filter conditions](https://developers.com/reference-link/post-database-query-filter). - Filter Filter `json:"filter,omitempty"` + Filter Filter `json:"filter,omitempty" url:"-"` // When supplied, orders the results based on the provided // [sort criteria](https://developers.com/reference-link/post-database-query-sort). - Sorts []Sort `json:"sorts,omitempty"` + Sorts []Sort `json:"sorts,omitempty" url:"-"` } type DatabasesQueryResponse struct { @@ -527,8 +531,6 @@ func (d *databasesClient) Retrieve(ctx context.Context, params DatabasesRetrieve err := d.restClient.New().Get(). Endpoint(strings.Replace(APIDatabasesRetrieveEndpoint, "{database_id}", params.DatabaseID, 1)). - QueryStruct(params). - BodyJSON(nil). Receive(ctx, &result, &failure) return &result, err // nolint:wrapcheck @@ -542,7 +544,6 @@ func (d *databasesClient) List(ctx context.Context, params DatabasesListParamete err := d.restClient.New().Get(). Endpoint(APIDatabasesListEndpoint). QueryStruct(params). - BodyJSON(params). Receive(ctx, &result, &failure) return &result, err // nolint:wrapcheck diff --git a/databases_test.go b/databases_test.go new file mode 100644 index 0000000..3e97294 --- /dev/null +++ b/databases_test.go @@ -0,0 +1,817 @@ +package notion + +import ( + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/mkfsn/notion-go/rest" + "github.com/stretchr/testify/assert" +) + +func Test_databasesClient_List(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params DatabasesListParameters + } + + type wants struct { + response *DatabasesListResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "List two databases in one page", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodGet, request.Method) + assert.Equal(t, "/v1/databases?page_size=2", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "results": [ + { + "object": "database", + "id": "668d797c-76fa-4934-9b05-ad288df2d136", + "properties": { + "Name": { + "type": "title", + "title": {} + }, + "Description": { + "type": "rich_text", + "rich_text": {} + } + } + }, + { + "object": "database", + "id": "74ba0cb2-732c-4d2f-954a-fcaa0d93a898", + "properties": { + "Name": { + "type": "title", + "title": {} + }, + "Description": { + "type": "rich_text", + "rich_text": {} + } + } + } + ], + "next_cursor": "MTY3NDE4NGYtZTdiYy00NzFlLWE0NjctODcxOTIyYWU3ZmM3", + "has_more": false + }`)) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: DatabasesListParameters{ + PaginationParameters: PaginationParameters{ + StartCursor: "", + PageSize: 2, + }, + }, + }, + wants: wants{ + response: &DatabasesListResponse{ + PaginatedList: PaginatedList{ + // FIXME: This should be ObjectTypeList but the example does not provide the object key-value + Object: "", + HasMore: false, + NextCursor: "MTY3NDE4NGYtZTdiYy00NzFlLWE0NjctODcxOTIyYWU3ZmM3", + }, + Results: []Database{ + { + Object: ObjectTypeDatabase, + ID: "668d797c-76fa-4934-9b05-ad288df2d136", + // FIXME: The example seems to have a invalid title thus I remove it for now, but need to check + // what is the expected title + Title: []RichText{}, + Properties: map[string]Property{ + "Name": &TitleProperty{ + baseProperty: baseProperty{ + ID: "", + Type: PropertyTypeTitle, + }, + Title: map[string]interface{}{}, + }, + // FIXME: The example seems to have a invalid type of the description thus I change it + // to `rich_text` for now, but need to check what is the expected type + "Description": &RichTextProperty{ + baseProperty: baseProperty{ + ID: "", + Type: PropertyTypeRichText, + }, + RichText: map[string]interface{}{}, + }, + }, + }, + { + Object: ObjectTypeDatabase, + ID: "74ba0cb2-732c-4d2f-954a-fcaa0d93a898", + // FIXME: The example seems to have a invalid title thus I remove it for now, but need to check + // what is the expected title + Title: []RichText{}, + Properties: map[string]Property{ + "Name": &TitleProperty{ + baseProperty: baseProperty{ + ID: "", + Type: PropertyTypeTitle, + }, + Title: map[string]interface{}{}, + }, + "Description": &RichTextProperty{ + baseProperty: baseProperty{ + ID: "", + Type: PropertyTypeRichText, + }, + RichText: map[string]interface{}{}, + }, + }, + }, + }, + }, + err: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() + + d := &databasesClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := d.List(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func Test_databasesClient_Query(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params DatabasesQueryParameters + } + + type wants struct { + response *DatabasesQueryResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Query database with filter and sort", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodPost, request.Method) + assert.Equal(t, "/v1/databases/897e5a76ae524b489fdfe71f5945d1af/query", request.RequestURI) + + expectedData := `{ + "filter": { + "or": [ + { + "property": "In stock", + "checkbox": { + "equals": true + } + }, + { + "property": "Cost of next trip", + "number": { + "greater_than_or_equal_to": 2 + } + } + ] + }, + "sorts": [ + { + "property": "Last ordered", + "direction": "ascending" + } + ] + }` + + b, err := ioutil.ReadAll(request.Body) + assert.NoError(t, err) + assert.JSONEq(t, expectedData, string(b)) + + writer.WriteHeader(http.StatusOK) + + _, err = writer.Write([]byte(`{ + "object": "list", + "results": [ + { + "object": "page", + "id": "2e01e904-febd-43a0-ad02-8eedb903a82c", + "created_time": "2020-03-17T19:10:04.968Z", + "last_edited_time": "2020-03-17T21:49:37.913Z", + "parent": { + "type": "database_id", + "database_id": "897e5a76-ae52-4b48-9fdf-e71f5945d1af" + }, + "archived": false, + "properties": { + "Recipes": { + "id": "Ai` + "`" + `L", + "type": "relation", + "relation": [ + { + "id": "796659b4-a5d9-4c64-a539-06ac5292779e" + }, + { + "id": "79e63318-f85a-4909-aceb-96a724d1021c" + } + ] + }, + "Cost of next trip": { + "id": "R}wl", + "type": "formula", + "formula": { + "type": "number", + "number": 2 + } + }, + "Last ordered": { + "id": "UsKi", + "type": "date", + "date": { + "start": "2020-10-07", + "end": null + } + }, + "In stock": { + "id": "{>U;", + "type": "checkbox", + "checkbox": false + } + } + } + ], + "has_more": false, + "next_cursor": null + }`)) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: DatabasesQueryParameters{ + PaginationParameters: PaginationParameters{}, + DatabaseID: "897e5a76ae524b489fdfe71f5945d1af", + Filter: CompoundFilter{ + Or: []Filter{ + &SingleCheckboxFilter{ + SinglePropertyFilter: SinglePropertyFilter{ + Property: "In stock", + }, + Checkbox: CheckboxFilter{ + Equals: true, + }, + }, + &SingleNumberFilter{ + SinglePropertyFilter: SinglePropertyFilter{ + Property: "Cost of next trip", + }, + Number: NumberFilter{ + GreaterThanOrEqualTo: newFloat64(2), + }, + }, + }, + }, + Sorts: []Sort{ + { + Property: "Last ordered", + Direction: SortDirectionAscending, + }, + }, + }, + }, + wants: wants{ + response: &DatabasesQueryResponse{ + PaginatedList: PaginatedList{ + Object: ObjectTypeList, + HasMore: false, + NextCursor: "", + }, + Results: []Page{ + { + Object: ObjectTypePage, + ID: "2e01e904-febd-43a0-ad02-8eedb903a82c", + Parent: &DatabaseParent{ + baseParent: baseParent{ + Type: ParentTypeDatabase, + }, + DatabaseID: "897e5a76-ae52-4b48-9fdf-e71f5945d1af", + }, + Properties: map[string]PropertyValue{ + "Recipes": &RelationPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "Ai`L", + Type: PropertyValueTypeRelation, + }, + Relation: []PageReference{ + {ID: "796659b4-a5d9-4c64-a539-06ac5292779e"}, + {ID: "79e63318-f85a-4909-aceb-96a724d1021c"}, + }, + }, + "Cost of next trip": &FormulaPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "R}wl", + Type: PropertyValueTypeFormula, + }, + Formula: &NumberFormulaValue{ + baseFormulaValue: baseFormulaValue{ + Type: FormulaValueTypeNumber, + }, + Number: newFloat64(2), + }, + }, + "Last ordered": &DatePropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "UsKi", + Type: PropertyValueTypeDate, + }, + Date: Date{ + Start: "2020-10-07", + End: nil, + }, + }, + "In stock": &CheckboxPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "{>U;", + Type: PropertyValueTypeCheckbox, + }, + Checkbox: false, + }, + }, + CreatedTime: time.Date(2020, 3, 17, 19, 10, 4, 968_000_000, time.UTC), + LastEditedTime: time.Date(2020, 3, 17, 21, 49, 37, 913_000_000, time.UTC), + Archived: false, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + + d := &databasesClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := d.Query(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func Test_databasesClient_Retrieve(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params DatabasesRetrieveParameters + } + + type wants struct { + response *DatabasesRetrieveResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Retrieve database", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodGet, request.Method) + assert.Equal(t, "/v1/databases/668d797c-76fa-4934-9b05-ad288df2d136", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "object": "database", + "id": "668d797c-76fa-4934-9b05-ad288df2d136", + "created_time": "2020-03-17T19:10:04.968Z", + "last_edited_time": "2020-03-17T21:49:37.913Z", + "title": [ + { + "type": "text", + "text": { + "content": "Grocery List", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Grocery List", + "href": null + } + ], + "properties": { + "Name": { + "id": "title", + "type": "title", + "title": {} + }, + "Description": { + "id": "J@cS", + "type": "rich_text", + "rich_text": {} + }, + "In stock": { + "id": "{xY` + "`" + `", + "type": "checkbox", + "checkbox": {} + }, + "Food group": { + "id": "TJmr", + "type": "select", + "select": { + "options": [ + { + "id": "96eb622f-4b88-4283-919d-ece2fbed3841", + "name": "🥦Vegetable", + "color": "green" + }, + { + "id": "bb443819-81dc-46fb-882d-ebee6e22c432", + "name": "🍎Fruit", + "color": "red" + }, + { + "id": "7da9d1b9-8685-472e-9da3-3af57bdb221e", + "name": "💪Protein", + "color": "yellow" + } + ] + } + }, + "Price": { + "id": "cU^N", + "type": "number", + "number": { + "format": "dollar" + } + }, + "Cost of next trip": { + "id": "p:sC", + "type": "formula", + "formula": { + "expression": "if(prop(\"In stock\"), 0, prop(\"Price\"))" + } + }, + "Last ordered": { + "id": "]\\R[", + "type": "date", + "date": {} + }, + "Meals": { + "type": "relation", + "relation": { + "database_id": "668d797c-76fa-4934-9b05-ad288df2d136", + "synced_property_name": null + } + }, + "Number of meals": { + "id": "Z\\Eh", + "type": "rollup", + "rollup": { + "rollup_property_name": "Name", + "relation_property_name": "Meals", + "rollup_property_id": "title", + "relation_property_id": "mxp^", + "function": "count" + } + }, + "Store availability": { + "type": "multi_select", + "multi_select": { + "options": [ + { + "id": "d209b920-212c-4040-9d4a-bdf349dd8b2a", + "name": "Duc Loi Market", + "color": "blue" + }, + { + "id": "70104074-0f91-467b-9787-00d59e6e1e41", + "name": "Rainbow Grocery", + "color": "gray" + }, + { + "id": "e6fd4f04-894d-4fa7-8d8b-e92d08ebb604", + "name": "Nijiya Market", + "color": "purple" + }, + { + "id": "6c3867c5-d542-4f84-b6e9-a420c43094e7", + "name": "Gus's Community Market", + "color": "yellow" + } + ] + } + }, + "+1": { + "id": "aGut", + "type": "people", + "people": {} + }, + "Photo": { + "id": "aTIT", + "type": "file", + "file": {} + } + } + }`)) + // FIXME: In the API reference https://developers.notion.com/reference/get-database + // the options in the multi_select property is an array of array which does not make sense + // to me, thus changing it to an array but need to verify this. + + // FIXME: In the API reference https://developers.notion.com/reference/get-database + // the Photo Property has `files` type but in the documentation, + // https://developers.notion.com/reference/database, the type should be `file`, + // Following the documentation, the JSON string above has been changed to type `file`. + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: DatabasesRetrieveParameters{ + DatabaseID: "668d797c-76fa-4934-9b05-ad288df2d136", + }, + }, + wants: wants{ + response: &DatabasesRetrieveResponse{ + Database: Database{ + Object: ObjectTypeDatabase, + ID: "668d797c-76fa-4934-9b05-ad288df2d136", + CreatedTime: time.Date(2020, 3, 17, 19, 10, 4, 968_000_000, time.UTC), + LastEditedTime: time.Date(2020, 3, 17, 21, 49, 37, 913_000_000, time.UTC), + Title: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Grocery List", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Grocery List", + Link: nil, + }, + }, + }, + Properties: map[string]Property{ + "Name": &TitleProperty{ + baseProperty: baseProperty{ + ID: "title", + Type: "title", + }, + Title: map[string]interface{}{}, + }, + // FIXME: This is different from the example of https://developers.notion.com/reference/get-database + // but in the API reference there's no `text` type, thus changing it to `rich_text`, but need + // to confirm what is the expected type. + "Description": &RichTextProperty{ + baseProperty: baseProperty{ + ID: "J@cS", + Type: PropertyTypeRichText, + }, + RichText: map[string]interface{}{}, + }, + "In stock": &CheckboxProperty{ + baseProperty: baseProperty{ + ID: "{xY`", + Type: PropertyTypeCheckbox, + }, + Checkbox: map[string]interface{}{}, + }, + "Food group": &SelectProperty{ + baseProperty: baseProperty{ + ID: "TJmr", + Type: PropertyTypeSelect, + }, + Select: SelectPropertyOption{ + Options: []SelectOption{ + { + ID: "96eb622f-4b88-4283-919d-ece2fbed3841", + Name: "🥦Vegetable", + Color: ColorGreen, + }, + { + ID: "bb443819-81dc-46fb-882d-ebee6e22c432", + Name: "🍎Fruit", + Color: ColorRed, + }, + { + ID: "7da9d1b9-8685-472e-9da3-3af57bdb221e", + Name: "💪Protein", + Color: ColorYellow, + }, + }, + }, + }, + "Price": &NumberProperty{ + baseProperty: baseProperty{ + ID: "cU^N", + Type: PropertyTypeNumber, + }, + Number: NumberPropertyOption{ + Format: NumberFormatDollar, + }, + }, + "Cost of next trip": &FormulaProperty{ + baseProperty: baseProperty{ + ID: "p:sC", + Type: PropertyTypeFormula, + }, + Formula: Formula{ + // FIXME: The example response in https://developers.notion.com/reference/get-database + // has the key `value` but in the API reference https://developers.notion.com/reference/database#formula-configuration + // the property name should be `expression`, need to check if this is expected. + Expression: `if(prop("In stock"), 0, prop("Price"))`, + }, + }, + "Last ordered": &DateProperty{ + baseProperty: baseProperty{ + ID: "]\\R[", + Type: PropertyTypeDate, + }, + Date: map[string]interface{}{}, + }, + "Meals": &RelationProperty{ + baseProperty: baseProperty{ + // FIXME: The example response in https://developers.notion.com/reference/get-database + // does not contain the ID, need to check if this is expected. + ID: "", + Type: PropertyTypeRelation, + }, + Relation: Relation{ + // FIXME: The key in the example is `database` but should be `database_id` instead, + // and need to check if which one is correct. + DatabaseID: "668d797c-76fa-4934-9b05-ad288df2d136", + }, + }, + "Number of meals": &RollupProperty{ + baseProperty: baseProperty{ + ID: "Z\\Eh", + Type: PropertyTypeRollup, + }, + Rollup: RollupPropertyOption{ + RelationPropertyName: "Meals", + RelationPropertyID: "mxp^", + RollupPropertyName: "Name", + RollupPropertyID: "title", + // FIXME: In the example the returned function is `count` but the possible values + // do not include `count`, thus need to confirm this. + Function: "count", + }, + }, + "Store availability": &MultiSelectProperty{ + baseProperty: baseProperty{ + // FIXME: The example response in https://developers.notion.com/reference/get-database + // does not contain the ID, need to check if this is expected. + ID: "", + Type: PropertyTypeMultiSelect, + }, + MultiSelect: MultiSelectPropertyOption{ + Options: []MultiSelectOption{ + { + ID: "d209b920-212c-4040-9d4a-bdf349dd8b2a", + Name: "Duc Loi Market", + Color: ColorBlue, + }, + { + ID: "70104074-0f91-467b-9787-00d59e6e1e41", + Name: "Rainbow Grocery", + Color: ColorGray, + }, + { + ID: "e6fd4f04-894d-4fa7-8d8b-e92d08ebb604", + Name: "Nijiya Market", + Color: ColorPurple, + }, + { + ID: "6c3867c5-d542-4f84-b6e9-a420c43094e7", + Name: "Gus's Community Market", + Color: ColorYellow, + }, + }, + }, + }, + "+1": &PeopleProperty{ + baseProperty: baseProperty{ + ID: "aGut", + Type: PropertyTypePeople, + }, + People: map[string]interface{}{}, + }, + "Photo": &FileProperty{ + baseProperty: baseProperty{ + ID: "aTIT", + Type: PropertyTypeFile, + }, + File: map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() + + d := &databasesClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := d.Retrieve(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func newFloat64(f float64) *float64 { + return &f +} diff --git a/pages.go b/pages.go index 3ef4495..dbe16d2 100644 --- a/pages.go +++ b/pages.go @@ -195,12 +195,14 @@ type MultiSelectPropertyValue struct { MultiSelect []MultiSelectPropertyValueOption `json:"multi_select"` } +type Date struct { + Start string `json:"start"` + End *string `json:"end"` +} + type DatePropertyValue struct { basePropertyValue - Date struct { - Start string `json:"start"` - End *string `json:"end"` - } `json:"date"` + Date Date `json:"date"` } type FormulaValue interface { @@ -257,6 +259,15 @@ func (f *FormulaPropertyValue) UnmarshalJSON(data []byte) error { return nil } +type PageReference struct { + ID string `json:"id"` +} + +type RelationPropertyValue struct { + basePropertyValue + Relation []PageReference `json:"relation"` +} + type RollupValueType interface { isRollupValueType() } @@ -292,11 +303,13 @@ type PeoplePropertyValue struct { People []User `json:"people"` } +type File struct { + Name string `json:"name"` +} + type FilesPropertyValue struct { basePropertyValue - Files []struct { - Name string `json:"name"` - } `json:"files"` + Files []File `json:"files"` } type CheckboxPropertyValue struct { @@ -518,6 +531,9 @@ func (p *propertyValueDecoder) UnmarshalJSON(data []byte) error { case PropertyValueTypeFormula: p.PropertyValue = &FormulaPropertyValue{} + case PropertyValueTypeRelation: + p.PropertyValue = &RelationPropertyValue{} + case PropertyValueTypeRollup: p.PropertyValue = &RollupPropertyValue{} diff --git a/search_test.go b/search_test.go index 56a2248..98b1377 100644 --- a/search_test.go +++ b/search_test.go @@ -171,7 +171,7 @@ func Test_searchClient_Search(t *testing.T) { Strikethrough: false, Underline: false, Code: false, - Color: DefaultColor, + Color: ColorDefault, }, }, Text: TextObject{ @@ -227,7 +227,7 @@ func Test_searchClient_Search(t *testing.T) { Strikethrough: false, Underline: false, Code: false, - Color: DefaultColor, + Color: ColorDefault, }, }, Text: TextObject{ From 824738e225d2a3566787399f097f5f3a3950784b Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 00:16:53 +0800 Subject: [PATCH 20/32] test: add blocks children list/append test (#6) --- blocks.go | 15 +- blocks_test.go | 538 +++++++++++++++++++++++++++++++++++++++++++++++++ databases.go | 3 +- 3 files changed, 548 insertions(+), 8 deletions(-) create mode 100644 blocks_test.go diff --git a/blocks.go b/blocks.go index 3b3e3c2..e11b19b 100644 --- a/blocks.go +++ b/blocks.go @@ -26,7 +26,7 @@ type BlockBase struct { // Date and time when this block was last updated. Formatted as an ISO 8601 date time string. LastEditedTime *time.Time `json:"last_edited_time,omitempty"` // Whether or not the block has children blocks nested within it. - HasChildren *bool `json:"has_children,omitempty"` + HasChildren bool `json:"has_children,omitempty"` } func (b BlockBase) isBlock() {} @@ -41,15 +41,17 @@ type HeadingBlock struct { } func (h *HeadingBlock) UnmarshalJSON(data []byte) error { - var text []richTextDecoder + var alias struct { + Text []richTextDecoder `json:"text"` + } - if err := json.Unmarshal(data, &text); err != nil { + if err := json.Unmarshal(data, &alias); err != nil { return fmt.Errorf("failed to unmarshal HeadingBlock: %w", err) } - h.Text = make([]RichText, 0, len(text)) + h.Text = make([]RichText, 0, len(alias.Text)) - for _, decoder := range text { + for _, decoder := range alias.Text { h.Text = append(h.Text, decoder.RichText) } @@ -166,7 +168,7 @@ type BlocksChildrenListParameters struct { PaginationParameters // Identifier for a block - BlockID string `json:"-"` + BlockID string `json:"-" url:"-"` } type BlocksChildrenListResponse struct { @@ -243,7 +245,6 @@ func (b *blocksChildrenClient) List(ctx context.Context, params BlocksChildrenLi err := b.restClient.New().Get(). Endpoint(strings.Replace(APIBlocksListChildrenEndpoint, "{block_id}", params.BlockID, 1)). QueryStruct(params). - BodyJSON(nil). Receive(ctx, &result, &failure) return &result, err // nolint:wrapcheck diff --git a/blocks_test.go b/blocks_test.go new file mode 100644 index 0000000..2be331d --- /dev/null +++ b/blocks_test.go @@ -0,0 +1,538 @@ +package notion + +import ( + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/mkfsn/notion-go/rest" + "github.com/stretchr/testify/assert" +) + +func Test_blocksChildrenClient_List(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params BlocksChildrenListParameters + } + + type wants struct { + response *BlocksChildrenListResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "List 3 block children in a page", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodGet, request.Method) + assert.Equal(t, "/v1/blocks/b55c9c91-384d-452b-81db-d1ef79372b75/children?page_size=100", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "object": "list", + "results": [ + { + "object": "block", + "id": "9bc30ad4-9373-46a5-84ab-0a7845ee52e6", + "created_time": "2021-03-16T16:31:00.000Z", + "last_edited_time": "2021-03-16T16:32:00.000Z", + "has_children": false, + "type": "heading_2", + "heading_2": { + "text": [ + { + "type": "text", + "text": { + "content": "Lacinato kale", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Lacinato kale", + "href": null + } + ] + } + }, + { + "object": "block", + "id": "7face6fd-3ef4-4b38-b1dc-c5044988eec0", + "created_time": "2021-03-16T16:34:00.000Z", + "last_edited_time": "2021-03-16T16:36:00.000Z", + "has_children": false, + "type": "paragraph", + "paragraph": { + "text": [ + { + "type": "text", + "text": { + "content": "Lacinato kale", + "link": { + "url": "https://en.wikipedia.org/wiki/Lacinato_kale" + } + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Lacinato kale", + "href": "https://en.wikipedia.org/wiki/Lacinato_kale" + }, + { + "type": "text", + "text": { + "content": " is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": " is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + "href": null + } + ] + } + }, + { + "object": "block", + "id": "7636e2c9-b6c1-4df1-aeae-3ebf0073c5cb", + "created_time": "2021-03-16T16:35:00.000Z", + "last_edited_time": "2021-03-16T16:36:00.000Z", + "has_children": true, + "type": "toggle", + "toggle": { + "text": [ + { + "type": "text", + "text": { + "content": "Recipes", + "link": null + }, + "annotations": { + "bold": true, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Recipes", + "href": null + } + ] + } + } + ], + "next_cursor": null, + "has_more": false + }`)) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: BlocksChildrenListParameters{ + PaginationParameters: PaginationParameters{ + StartCursor: "", + PageSize: 100, + }, + BlockID: "b55c9c91-384d-452b-81db-d1ef79372b75", + }, + }, + wants: wants{ + response: &BlocksChildrenListResponse{ + PaginatedList: PaginatedList{ + Object: ObjectTypeList, + HasMore: false, + NextCursor: "", + }, + Results: []Block{ + &Heading2Block{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + ID: "9bc30ad4-9373-46a5-84ab-0a7845ee52e6", + Type: BlockTypeHeading2, + CreatedTime: newTime(time.Date(2021, 3, 16, 16, 31, 0, 0, time.UTC)), + LastEditedTime: newTime(time.Date(2021, 3, 16, 16, 32, 0, 0, time.UTC)), + HasChildren: false, + }, + Heading2: HeadingBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Lacinato kale", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Lacinato kale", + Link: nil, + }, + }, + }, + }, + }, + &ParagraphBlock{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + ID: "7face6fd-3ef4-4b38-b1dc-c5044988eec0", + Type: BlockTypeParagraph, + CreatedTime: newTime(time.Date(2021, 3, 16, 16, 34, 0, 0, time.UTC)), + LastEditedTime: newTime(time.Date(2021, 3, 16, 16, 36, 0, 0, time.UTC)), + HasChildren: false, + }, + Paragraph: RichTextBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Lacinato kale", + Href: "https://en.wikipedia.org/wiki/Lacinato_kale", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Lacinato kale", + Link: &Link{ + URL: "https://en.wikipedia.org/wiki/Lacinato_kale", + }, + }, + }, + + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: " is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: " is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + Link: nil, + }, + }, + }, + Children: []Block{}, + }, + }, + &ToggleBlock{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + ID: "7636e2c9-b6c1-4df1-aeae-3ebf0073c5cb", + Type: BlockTypeToggle, + CreatedTime: newTime(time.Date(2021, 3, 16, 16, 35, 0, 0, time.UTC)), + LastEditedTime: newTime(time.Date(2021, 3, 16, 16, 36, 0, 0, time.UTC)), + HasChildren: true, + }, + Toggle: RichTextBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Recipes", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: true, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Recipes", + Link: nil, + }, + }, + }, + Children: []Block{}, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() + + d := &blocksChildrenClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := d.List(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func Test_blocksChildrenClient_Append(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params BlocksChildrenAppendParameters + } + + type wants struct { + response *BlocksChildrenAppendResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Append children successfully", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodPatch, request.Method) + assert.Equal(t, "/v1/blocks/9bd15f8d-8082-429b-82db-e6c4ea88413b/children", request.RequestURI) + assert.Equal(t, "application/json", request.Header.Get("Content-Type")) + + expectedData := `{ + "children": [ + { + "object": "block", + "type": "heading_2", + "heading_2": { + "text": [{ "type": "text", "text": { "content": "Lacinato kale" } }] + } + }, + { + "object": "block", + "type": "paragraph", + "paragraph": { + "text": [ + { + "type": "text", + "text": { + "content": "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + "link": { "url": "https://en.wikipedia.org/wiki/Lacinato_kale" } + } + } + ] + } + } + ] + }` + b, err := ioutil.ReadAll(request.Body) + assert.NoError(t, err) + assert.JSONEq(t, expectedData, string(b)) + + writer.WriteHeader(http.StatusOK) + + _, err = writer.Write([]byte(`{ + "object": "block", + "id": "9bd15f8d-8082-429b-82db-e6c4ea88413b", + "created_time": "2020-03-17T19:10:04.968Z", + "last_edited_time": "2020-03-17T21:49:37.913Z", + "has_children": true, + "type": "toggle", + "toggle": { + "text": [ + { + "type": "text", + "text": { + "content": "Recipes", + "link": null + }, + "annotations": { + "bold": true, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Recipes", + "href": null + } + ] + } + }`)) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: BlocksChildrenAppendParameters{ + BlockID: "9bd15f8d-8082-429b-82db-e6c4ea88413b", + Children: []Block{ + &Heading2Block{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + Type: BlockTypeHeading2, + }, + Heading2: HeadingBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + Type: RichTextTypeText, + }, + Text: TextObject{ + Content: "Lacinato kale", + }, + }, + }, + }, + }, + &ParagraphBlock{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + Type: BlockTypeParagraph, + }, + Paragraph: RichTextBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + Type: RichTextTypeText, + }, + Text: TextObject{ + Content: "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + Link: &Link{ + URL: "https://en.wikipedia.org/wiki/Lacinato_kale", + }, + }, + }, + }, + }, + }, + }, + }, + }, + wants: wants{ + response: &BlocksChildrenAppendResponse{ + Block: &ToggleBlock{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + ID: "9bd15f8d-8082-429b-82db-e6c4ea88413b", + Type: BlockTypeToggle, + CreatedTime: newTime(time.Date(2020, 3, 17, 19, 10, 4, 968_000_000, time.UTC)), + LastEditedTime: newTime(time.Date(2020, 3, 17, 21, 49, 37, 913_000_000, time.UTC)), + HasChildren: true, + }, + Toggle: RichTextBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Recipes", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: true, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Recipes", + Link: nil, + }, + }, + }, + Children: []Block{}, + }, + }, + }, + err: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() + + d := &blocksChildrenClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := d.Append(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func newTime(t time.Time) *time.Time { + return &t +} diff --git a/databases.go b/databases.go index e897f79..b337543 100644 --- a/databases.go +++ b/databases.go @@ -86,7 +86,8 @@ type BaseRichText struct { func (r BaseRichText) isRichText() {} type Link struct { - Type string `json:"type"` + // TODO: What is this? Is this still in used? + Type string `json:"type,omitempty"` URL string `json:"url"` } From 860d08b7b0ca7915ac5918f633559202b28780a6 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 00:31:51 +0800 Subject: [PATCH 21/32] test: HTTPError (#7) --- errors.go | 21 +++++++++++++++++++-- errors_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 errors_test.go diff --git a/errors.go b/errors.go index 7226b72..aa99303 100644 --- a/errors.go +++ b/errors.go @@ -9,9 +9,26 @@ var ( ErrUnknown = errors.New("unknown") ) +// ErrorCode https://developers.notion.com/reference/errors +type ErrorCode string + +const ( + ErrorCodeInvalidJSON ErrorCode = "invalid_json" + ErrorCodeInvalidRequestURI ErrorCode = "invalid_request_url" + ErrorCodeInvalidRequest ErrorCode = "invalid_request" + ErrorCodeValidationError ErrorCode = "validation_error" + ErrorCodeUnauthorized ErrorCode = "unauthorized" + ErrorCodeRestrictedResource ErrorCode = "restricted_resource" + ErrorCodeObjectNotFound ErrorCode = "object_not_found" + ErrorCodeConflictError ErrorCode = "conflict_error" + ErrorCodeRateLimited ErrorCode = "rate_limited" + ErrorCodeInternalServerError ErrorCode = "internal_server_error" + ErrorCodeServiceUnavailable ErrorCode = "service_unavailable" +) + type HTTPError struct { - Code string `json:"code"` - Message string `json:"message"` + Code ErrorCode `json:"code"` + Message string `json:"message"` } func (e HTTPError) Error() string { diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 0000000..ba4fdf0 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,36 @@ +package notion + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHTTPError_Error(t *testing.T) { + type fields struct { + Code ErrorCode + Message string + } + tests := []struct { + fields fields + wantError string + }{ + { + fields: fields{ + Code: ErrorCodeInvalidJSON, + Message: "missing ]", + }, + wantError: "Code: invalid_json, Message: missing ]", + }, + } + for _, tt := range tests { + t.Run(string(tt.fields.Code), func(t *testing.T) { + e := HTTPError{ + Code: tt.fields.Code, + Message: tt.fields.Message, + } + + assert.EqualError(t, e, tt.wantError) + }) + } +} From 518336929495b4ee7676d8c2750623308b18f034 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 00:52:27 +0800 Subject: [PATCH 22/32] fix: typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7cd6658..9fcd29f 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,10 @@ go get -u github.com/mkfsn/notion-go c := notion.New("") // Retrieve block children -c.Blocks().List(...) +c.Blocks().Children().List(...) // Append block children -c.Blocks().Append(...) +c.Blocks().Children().Append(...) // List databases c.Databases().List(...) From 06527f843ba004bfc784849bb50e172def46f01c Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 21:29:54 +0800 Subject: [PATCH 23/32] test: add pages retrieve/create/update test (#8) --- databases.go | 2 +- pages.go | 4 +- pages_test.go | 728 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 730 insertions(+), 4 deletions(-) create mode 100644 pages_test.go diff --git a/databases.go b/databases.go index b337543..11c474a 100644 --- a/databases.go +++ b/databases.go @@ -77,7 +77,7 @@ type BaseRichText struct { // (Optional) The URL of any link or internal Notion mention in this text, if any. Href string `json:"href,omitempty"` // Type of this rich text object. - Type RichTextType `json:"type"` + Type RichTextType `json:"type,omitempty"` // All annotations that apply to this rich text. // Annotations include colors and bold/italics/underline/strikethrough. Annotations *Annotations `json:"annotations,omitempty"` diff --git a/pages.go b/pages.go index dbe16d2..69d1331 100644 --- a/pages.go +++ b/pages.go @@ -353,7 +353,7 @@ type LastEditedByPropertyValue struct { } type PagesRetrieveParameters struct { - PageID string + PageID string `json:"-" url:"-"` } type PagesRetrieveResponse struct { @@ -405,8 +405,6 @@ func (p *pagesClient) Retrieve(ctx context.Context, params PagesRetrieveParamete err := p.restClient.New().Get(). Endpoint(strings.Replace(APIPagesRetrieveEndpoint, "{page_id}", params.PageID, 1)). - QueryStruct(params). - BodyJSON(nil). Receive(ctx, &result, &failure) return &result, err // nolint:wrapcheck diff --git a/pages_test.go b/pages_test.go new file mode 100644 index 0000000..a00c841 --- /dev/null +++ b/pages_test.go @@ -0,0 +1,728 @@ +package notion + +import ( + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/mkfsn/notion-go/rest" + "github.com/stretchr/testify/assert" +) + +func Test_pagesClient_Retrieve(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params PagesRetrieveParameters + } + + type wants struct { + response *PagesRetrieveResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Retrieve a page by page_id", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodGet, request.Method) + assert.Equal(t, "/v1/pages/b55c9c91-384d-452b-81db-d1ef79372b75", request.RequestURI) + + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write([]byte(`{ + "object": "page", + "id": "b55c9c91-384d-452b-81db-d1ef79372b75", + "created_time": "2020-03-17T19:10:04.968Z", + "last_edited_time": "2020-03-17T21:49:37.913Z", + "properties": { + "Name": { + "id":"title", + "type":"title", + "title": [ + { + "type": "text", + "text": {"content":"Avocado","link":null}, + "annotations": { + "bold":true, + "italic":false, + "strikethrough":false, + "underline":false, + "code":false, + "color":"default" + } + } + ] + }, + "Description": { + "type":"rich_text", + "rich_text": [ + { + "type": "text", + "text": {"content":"Persea americana","link":null}, + "annotations":{ + "bold":false, + "italic":false, + "strikethrough":false, + "underline":false, + "code":false, + "color":"default" + } + } + ] + } + } + }`)) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: PagesRetrieveParameters{ + PageID: "b55c9c91-384d-452b-81db-d1ef79372b75", + }, + }, + wants: wants{ + response: &PagesRetrieveResponse{ + Page: Page{ + Object: ObjectTypePage, + ID: "b55c9c91-384d-452b-81db-d1ef79372b75", + Parent: nil, + Properties: map[string]PropertyValue{ + "Name": &TitlePropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "title", + Type: PropertyValueTypeTitle, + }, + Title: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: true, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Avocado", + }, + }, + }, + }, + "Description": &RichTextPropertyValue{ + basePropertyValue: basePropertyValue{ + Type: PropertyValueTypeRichText, + }, + RichText: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Persea americana", + Link: nil, + }, + }, + }, + }, + }, + CreatedTime: time.Date(2020, 3, 17, 19, 10, 4, 968_000_000, time.UTC), + LastEditedTime: time.Date(2020, 3, 17, 21, 49, 37, 913_000_000, time.UTC), + Archived: false, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() + + p := &pagesClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := p.Retrieve(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func Test_pagesClient_Create(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params PagesCreateParameters + } + + type wants struct { + response *PagesCreateResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Create a new page", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodPost, request.Method) + assert.Equal(t, "/v1/pages", request.RequestURI) + assert.Equal(t, "application/json", request.Header.Get("Content-Type")) + + expectedData := `{ + "parent": { "database_id": "48f8fee9cd794180bc2fec0398253067" }, + "properties": { + "Name": { + "title": [ + { + "text": { + "content": "Tuscan Kale" + } + } + ] + }, + "Description": { + "rich_text": [ + { + "text": { + "content": "A dark green leafy vegetable" + } + } + ] + }, + "Food group": { + "select": { + "name": "Vegetable" + } + }, + "Price": { "number": 2.5 } + }, + "children": [ + { + "object": "block", + "type": "heading_2", + "heading_2": { + "text": [{ "type": "text", "text": { "content": "Lacinato kale" } }] + } + }, + { + "object": "block", + "type": "paragraph", + "paragraph": { + "text": [ + { + "type": "text", + "text": { + "content": "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + "link": { "url": "https://en.wikipedia.org/wiki/Lacinato_kale" } + } + } + ] + } + } + ] + }` + b, err := ioutil.ReadAll(request.Body) + assert.NoError(t, err) + assert.JSONEq(t, expectedData, string(b)) + + writer.WriteHeader(http.StatusOK) + + _, err = writer.Write([]byte(`{ + "object": "page", + "id": "251d2b5f-268c-4de2-afe9-c71ff92ca95c", + "created_time": "2020-03-17T19:10:04.968Z", + "last_edited_time": "2020-03-17T21:49:37.913Z", + "parent": { + "type": "database_id", + "database_id": "48f8fee9-cd79-4180-bc2f-ec0398253067" + }, + "archived": false, + "properties": { + "Recipes": { + "id": "Ai` + "`" + `L", + "type": "relation", + "relation": [] + }, + "Cost of next trip": { + "id": "R}wl", + "type": "formula", + "formula": { + "type": "number", + "number": null + } + }, + "Photos": { + "id": "d:Cb", + "type": "files", + "files": [] + }, + "Store availability": { + "id": "jrFQ", + "type": "multi_select", + "multi_select": [] + }, + "+1": { + "id": "k?CE", + "type": "people", + "people": [] + }, + "Description": { + "id": "rT{n", + "type": "rich_text", + "rich_text": [] + }, + "In stock": { + "id": "{>U;", + "type": "checkbox", + "checkbox": false + }, + "Name": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Tuscan Kale", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Tuscan Kale", + "href": null + } + ] + } + } + }`)) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: PagesCreateParameters{ + Parent: &DatabaseParentInput{ + DatabaseID: "48f8fee9cd794180bc2fec0398253067", + }, + Properties: map[string]PropertyValue{ + "Name": &TitlePropertyValue{ + Title: []RichText{ + &RichTextText{ + Text: TextObject{ + Content: "Tuscan Kale", + }, + }, + }, + }, + "Description": &RichTextPropertyValue{ + basePropertyValue: basePropertyValue{}, + RichText: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{}, + Text: TextObject{ + Content: "A dark green leafy vegetable", + }, + }, + }, + }, + "Food group": &SelectPropertyValue{ + basePropertyValue: basePropertyValue{}, + Select: SelectPropertyValueOption{ + Name: "Vegetable", + }, + }, + "Price": &NumberPropertyValue{ + Number: 2.5, + }, + }, + Children: []Block{ + &Heading2Block{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + Type: BlockTypeHeading2, + }, + Heading2: HeadingBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + Type: RichTextTypeText, + }, + Text: TextObject{ + Content: "Lacinato kale", + }, + }, + }, + }, + }, + &ParagraphBlock{ + BlockBase: BlockBase{ + Object: ObjectTypeBlock, + Type: BlockTypeParagraph, + }, + Paragraph: RichTextBlock{ + Text: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + Type: RichTextTypeText, + }, + Text: TextObject{ + Content: "Lacinato kale is a variety of kale with a long tradition in Italian cuisine, especially that of Tuscany. It is also known as Tuscan kale, Italian kale, dinosaur kale, kale, flat back kale, palm tree kale, or black Tuscan palm.", + Link: &Link{ + URL: "https://en.wikipedia.org/wiki/Lacinato_kale", + }, + }, + }, + }, + }, + }, + }, + }, + }, + wants: wants{ + response: &PagesCreateResponse{ + Page: Page{ + Object: ObjectTypePage, + ID: "251d2b5f-268c-4de2-afe9-c71ff92ca95c", + Parent: &DatabaseParent{ + baseParent: baseParent{ + Type: ParentTypeDatabase, + }, + DatabaseID: "48f8fee9-cd79-4180-bc2f-ec0398253067", + }, + Properties: map[string]PropertyValue{ + "Recipes": &RelationPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "Ai`L", + Type: PropertyValueTypeRelation, + }, + Relation: []PageReference{}, + }, + "Cost of next trip": &FormulaPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "R}wl", + Type: PropertyValueTypeFormula, + }, + Formula: &NumberFormulaValue{ + baseFormulaValue: baseFormulaValue{ + Type: FormulaValueTypeNumber, + }, + Number: nil, + }, + }, + "Photos": &FilesPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "d:Cb", + Type: PropertyValueTypeFiles, + }, + Files: []File{}, + }, + "Store availability": &MultiSelectPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "jrFQ", + Type: PropertyValueTypeMultiSelect, + }, + MultiSelect: []MultiSelectPropertyValueOption{}, + }, + "+1": &PeoplePropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "k?CE", + Type: PropertyValueTypePeople, + }, + People: []User{}, + }, + "Description": &RichTextPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "rT{n", + Type: PropertyValueTypeRichText, + }, + RichText: []RichText{}, + }, + "In stock": &CheckboxPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "{>U;", + Type: PropertyValueTypeCheckbox, + }, + Checkbox: false, + }, + "Name": &TitlePropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "title", + Type: PropertyValueTypeTitle, + }, + Title: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Tuscan Kale", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Tuscan Kale", + Link: nil, + }, + }, + }, + }, + }, + CreatedTime: time.Date(2020, 3, 17, 19, 10, 04, 968_000_000, time.UTC), + LastEditedTime: time.Date(2020, 3, 17, 21, 49, 37, 913_000_000, time.UTC), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() + + p := &pagesClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := p.Create(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} + +func Test_pagesClient_Update(t *testing.T) { + type fields struct { + restClient rest.Interface + mockHTTPHandler http.Handler + } + + type args struct { + ctx context.Context + params PagesUpdateParameters + } + + type wants struct { + response *PagesUpdateResponse + err error + } + + type test struct { + name string + fields fields + args args + wants wants + } + + tests := []test{ + { + name: "Update a page properties", + fields: fields{ + restClient: rest.New(), + mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, http.MethodPatch, request.Method) + assert.Equal(t, "/v1/pages/60bdc8bd-3880-44b8-a9cd-8a145b3ffbd7", request.RequestURI) + assert.Equal(t, "application/json", request.Header.Get("Content-Type")) + + expectedData := `{ + "properties": { + "In stock": { "checkbox": true } + } + }` + b, err := ioutil.ReadAll(request.Body) + assert.NoError(t, err) + assert.JSONEq(t, expectedData, string(b)) + + writer.WriteHeader(http.StatusOK) + + _, err = writer.Write([]byte(`{ + "object": "page", + "id": "60bdc8bd-3880-44b8-a9cd-8a145b3ffbd7", + "created_time": "2020-03-17T19:10:04.968Z", + "last_edited_time": "2020-03-17T21:49:37.913Z", + "parent": { + "type": "database_id", + "database_id": "48f8fee9-cd79-4180-bc2f-ec0398253067" + }, + "archived": false, + "properties": { + "In stock": { + "id": "{>U;", + "type": "checkbox", + "checkbox": true + }, + "Name": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Avocado", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Avocado", + "href": null + } + ] + } + } + }`)) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.Background(), + params: PagesUpdateParameters{ + PageID: "60bdc8bd-3880-44b8-a9cd-8a145b3ffbd7", + Properties: map[string]PropertyValue{ + "In stock": &CheckboxPropertyValue{ + Checkbox: true, + }, + }, + }, + }, + wants: wants{ + response: &PagesUpdateResponse{ + Page: Page{ + Object: ObjectTypePage, + ID: "60bdc8bd-3880-44b8-a9cd-8a145b3ffbd7", + Parent: &DatabaseParent{ + baseParent: baseParent{ + Type: ParentTypeDatabase, + }, + DatabaseID: "48f8fee9-cd79-4180-bc2f-ec0398253067", + }, + Properties: map[string]PropertyValue{ + "In stock": &CheckboxPropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "{>U;", + Type: PropertyValueTypeCheckbox, + }, + Checkbox: true, + }, + "Name": &TitlePropertyValue{ + basePropertyValue: basePropertyValue{ + ID: "title", + Type: PropertyValueTypeTitle, + }, + Title: []RichText{ + &RichTextText{ + BaseRichText: BaseRichText{ + PlainText: "Avocado", + Href: "", + Type: RichTextTypeText, + Annotations: &Annotations{ + Bold: false, + Italic: false, + Strikethrough: false, + Underline: false, + Code: false, + Color: ColorDefault, + }, + }, + Text: TextObject{ + Content: "Avocado", + Link: nil, + }, + }, + }, + }, + }, + CreatedTime: time.Date(2020, 3, 17, 19, 10, 4, 968_000_000, time.UTC), + LastEditedTime: time.Date(2020, 3, 17, 21, 49, 37, 913_000_000, time.UTC), + Archived: false, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() + + p := &pagesClient{ + restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), + } + got, err := p.Update(tt.args.ctx, tt.args.params) + if tt.wants.err != nil { + assert.ErrorIs(t, err, tt.wants.err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wants.response, got) + }) + } +} From ffe1e1397ec088ada6e38fca4b916e9050820398 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 22:10:04 +0800 Subject: [PATCH 24/32] test: use New() to create SUT (#10) --- blocks_test.go | 32 +++++++++++++++++++++++-------- databases_test.go | 48 +++++++++++++++++++++++++++++++++++------------ pages_test.go | 48 +++++++++++++++++++++++++++++++++++------------ search_test.go | 14 +++++++++++--- users_test.go | 37 ++++++++++++++++++++++++++++-------- 5 files changed, 136 insertions(+), 43 deletions(-) diff --git a/blocks_test.go b/blocks_test.go index 2be331d..95cc999 100644 --- a/blocks_test.go +++ b/blocks_test.go @@ -16,6 +16,7 @@ func Test_blocksChildrenClient_List(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -40,7 +41,12 @@ func Test_blocksChildrenClient_List(t *testing.T) { name: "List 3 block children in a page", fields: fields{ restClient: rest.New(), + authToken: "59642182-381f-458b-8144-4cc0750383c1", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 59642182-381f-458b-8144-4cc0750383c1", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodGet, request.Method) assert.Equal(t, "/v1/blocks/b55c9c91-384d-452b-81db-d1ef79372b75/children?page_size=100", request.RequestURI) @@ -313,10 +319,12 @@ func Test_blocksChildrenClient_List(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) defer mockHTTPServer.Close() - d := &blocksChildrenClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := d.List(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Blocks().Children().List(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return @@ -332,6 +340,7 @@ func Test_blocksChildrenClient_Append(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -356,7 +365,12 @@ func Test_blocksChildrenClient_Append(t *testing.T) { name: "Append children successfully", fields: fields{ restClient: rest.New(), + authToken: "54b85fbe-69c3-4726-88a6-0070bcaea59d", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 54b85fbe-69c3-4726-88a6-0070bcaea59d", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodPatch, request.Method) assert.Equal(t, "/v1/blocks/9bd15f8d-8082-429b-82db-e6c4ea88413b/children", request.RequestURI) assert.Equal(t, "application/json", request.Header.Get("Content-Type")) @@ -518,10 +532,12 @@ func Test_blocksChildrenClient_Append(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) defer mockHTTPServer.Close() - d := &blocksChildrenClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := d.Append(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Blocks().Children().Append(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return diff --git a/databases_test.go b/databases_test.go index 3e97294..5fb4d23 100644 --- a/databases_test.go +++ b/databases_test.go @@ -16,6 +16,7 @@ func Test_databasesClient_List(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -40,7 +41,12 @@ func Test_databasesClient_List(t *testing.T) { name: "List two databases in one page", fields: fields{ restClient: rest.New(), + authToken: "3e83b541-190b-4450-bfcc-835a7804d5b1", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 3e83b541-190b-4450-bfcc-835a7804d5b1", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodGet, request.Method) assert.Equal(t, "/v1/databases?page_size=2", request.RequestURI) @@ -161,10 +167,12 @@ func Test_databasesClient_List(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) defer mockHTTPServer.Close() - d := &databasesClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := d.List(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Databases().List(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return @@ -180,6 +188,7 @@ func Test_databasesClient_Query(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -204,7 +213,12 @@ func Test_databasesClient_Query(t *testing.T) { name: "Query database with filter and sort", fields: fields{ restClient: rest.New(), + authToken: "22e5435c-01f7-4d68-ad8c-203948e96b0b", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 22e5435c-01f7-4d68-ad8c-203948e96b0b", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodPost, request.Method) assert.Equal(t, "/v1/databases/897e5a76ae524b489fdfe71f5945d1af/query", request.RequestURI) @@ -400,10 +414,12 @@ func Test_databasesClient_Query(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) - d := &databasesClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := d.Query(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Databases().Query(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return @@ -419,6 +435,7 @@ func Test_databasesClient_Retrieve(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -443,7 +460,12 @@ func Test_databasesClient_Retrieve(t *testing.T) { name: "Retrieve database", fields: fields{ restClient: rest.New(), + authToken: "cf0d2546-ac5c-4d2e-8c39-80dad88a5208", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer cf0d2546-ac5c-4d2e-8c39-80dad88a5208", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodGet, request.Method) assert.Equal(t, "/v1/databases/668d797c-76fa-4934-9b05-ad288df2d136", request.RequestURI) @@ -797,10 +819,12 @@ func Test_databasesClient_Retrieve(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) defer mockHTTPServer.Close() - d := &databasesClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := d.Retrieve(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Databases().Retrieve(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return diff --git a/pages_test.go b/pages_test.go index a00c841..14fd833 100644 --- a/pages_test.go +++ b/pages_test.go @@ -16,6 +16,7 @@ func Test_pagesClient_Retrieve(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -40,7 +41,12 @@ func Test_pagesClient_Retrieve(t *testing.T) { name: "Retrieve a page by page_id", fields: fields{ restClient: rest.New(), + authToken: "6cf01c0d-3b5e-49ec-a45e-43c1879cf41e", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 6cf01c0d-3b5e-49ec-a45e-43c1879cf41e", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodGet, request.Method) assert.Equal(t, "/v1/pages/b55c9c91-384d-452b-81db-d1ef79372b75", request.RequestURI) @@ -168,10 +174,12 @@ func Test_pagesClient_Retrieve(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) defer mockHTTPServer.Close() - p := &pagesClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := p.Retrieve(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Pages().Retrieve(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return @@ -187,6 +195,7 @@ func Test_pagesClient_Create(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -211,7 +220,12 @@ func Test_pagesClient_Create(t *testing.T) { name: "Create a new page", fields: fields{ restClient: rest.New(), + authToken: "0747d2ee-13f0-47b1-950f-511d2c87180d", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 0747d2ee-13f0-47b1-950f-511d2c87180d", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodPost, request.Method) assert.Equal(t, "/v1/pages", request.RequestURI) assert.Equal(t, "application/json", request.Header.Get("Content-Type")) @@ -538,10 +552,12 @@ func Test_pagesClient_Create(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) defer mockHTTPServer.Close() - p := &pagesClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := p.Create(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Pages().Create(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return @@ -557,6 +573,7 @@ func Test_pagesClient_Update(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -581,7 +598,12 @@ func Test_pagesClient_Update(t *testing.T) { name: "Update a page properties", fields: fields{ restClient: rest.New(), + authToken: "4ad4d7a9-8b66-4dda-b9a1-2bc98134ee14", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 4ad4d7a9-8b66-4dda-b9a1-2bc98134ee14", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodPatch, request.Method) assert.Equal(t, "/v1/pages/60bdc8bd-3880-44b8-a9cd-8a145b3ffbd7", request.RequestURI) assert.Equal(t, "application/json", request.Header.Get("Content-Type")) @@ -712,10 +734,12 @@ func Test_pagesClient_Update(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) defer mockHTTPServer.Close() - p := &pagesClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } - got, err := p.Update(tt.args.ctx, tt.args.params) + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) + + got, err := sut.Pages().Update(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return diff --git a/search_test.go b/search_test.go index 98b1377..5fa1575 100644 --- a/search_test.go +++ b/search_test.go @@ -15,6 +15,7 @@ func Test_searchClient_Search(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -39,7 +40,12 @@ func Test_searchClient_Search(t *testing.T) { name: "Search two objects in one page", fields: fields{ restClient: rest.New(), + authToken: "39686a40-3364-4499-8639-185740546d42", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 39686a40-3364-4499-8639-185740546d42", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodPost, request.Method) assert.Equal(t, "/v1/search?page_size=2", request.RequestURI) @@ -251,10 +257,12 @@ func Test_searchClient_Search(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() - sut := &searchClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) got, err := sut.Search(tt.args.ctx, tt.args.params) if tt.wants.err != nil { diff --git a/users_test.go b/users_test.go index 10b691a..086ec7b 100644 --- a/users_test.go +++ b/users_test.go @@ -14,6 +14,7 @@ func Test_usersClient_Retrieve(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -38,7 +39,12 @@ func Test_usersClient_Retrieve(t *testing.T) { name: "Bot User", fields: fields{ restClient: rest.New(), + authToken: "033dcdcf-8252-49f4-826c-e795fcab0ad2", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 033dcdcf-8252-49f4-826c-e795fcab0ad2", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodGet, request.Method) assert.Equal(t, "/v1/users/9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57", request.RequestURI) @@ -80,7 +86,12 @@ func Test_usersClient_Retrieve(t *testing.T) { name: "Person User", fields: fields{ restClient: rest.New(), + authToken: "033dcdcf-8252-49f4-826c-e795fcab0ad2", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 033dcdcf-8252-49f4-826c-e795fcab0ad2", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodGet, request.Method) assert.Equal(t, "/v1/users/d40e767c-d7af-4b18-a86d-55c61f1e39a4", request.RequestURI) @@ -124,12 +135,14 @@ func Test_usersClient_Retrieve(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() - sut := &usersClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) - got, err := sut.Retrieve(tt.args.ctx, tt.args.params) + got, err := sut.Users().Retrieve(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return @@ -145,6 +158,7 @@ func Test_usersClient_List(t *testing.T) { type fields struct { restClient rest.Interface mockHTTPHandler http.Handler + authToken string } type args struct { @@ -169,7 +183,12 @@ func Test_usersClient_List(t *testing.T) { name: "List two users in one page", fields: fields{ restClient: rest.New(), + authToken: "2a966a7a-6e97-4b2c-abb2-c0eba4dbcb5f", mockHTTPHandler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.Equal(t, DefaultNotionVersion, request.Header.Get("Notion-Version")) + assert.Equal(t, DefaultUserAgent, request.Header.Get("User-Agent")) + assert.Equal(t, "Bearer 2a966a7a-6e97-4b2c-abb2-c0eba4dbcb5f", request.Header.Get("Authorization")) + assert.Equal(t, http.MethodGet, request.Method) assert.Equal(t, "/v1/users?page_size=2", request.RequestURI) @@ -248,12 +267,14 @@ func Test_usersClient_List(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockHTTPServer := httptest.NewServer(tt.fields.mockHTTPHandler) + defer mockHTTPServer.Close() - sut := &usersClient{ - restClient: tt.fields.restClient.BaseURL(mockHTTPServer.URL), - } + sut := New( + tt.fields.authToken, + WithBaseURL(mockHTTPServer.URL), + ) - got, err := sut.List(tt.args.ctx, tt.args.params) + got, err := sut.Users().List(tt.args.ctx, tt.args.params) if tt.wants.err != nil { assert.ErrorIs(t, err, tt.wants.err) return From d8debc67c72974811414a57bc02caa1557a64afc Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 22:32:11 +0800 Subject: [PATCH 25/32] test: add settings test (#11) --- api.go | 20 ------------------ api_internal_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 api_internal_test.go diff --git a/api.go b/api.go index 9449cbd..799f6ad 100644 --- a/api.go +++ b/api.go @@ -68,42 +68,22 @@ func New(authToken string, setters ...APISetting) *API { } func (c *API) Users() UsersInterface { - if c == nil { - return nil - } - return c.usersClient } func (c *API) Databases() DatabasesInterface { - if c == nil { - return nil - } - return c.databasesClient } func (c *API) Pages() PagesInterface { - if c == nil { - return nil - } - return c.pagesClient } func (c *API) Blocks() BlocksInterface { - if c == nil { - return nil - } - return c.blocksClient } func (c *API) Search(ctx context.Context, params SearchParameters) (*SearchResponse, error) { - if c == nil { - return nil, ErrUnknown - } - return c.searchClient.Search(ctx, params) } diff --git a/api_internal_test.go b/api_internal_test.go new file mode 100644 index 0000000..c073135 --- /dev/null +++ b/api_internal_test.go @@ -0,0 +1,48 @@ +package notion + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWithUserAgent(t *testing.T) { + var settings apiSettings + + userAgent := "test-user-agent" + + WithUserAgent("test-user-agent")(&settings) + + assert.Equal(t, settings.userAgent, userAgent) +} + +func TestWithBaseURL(t *testing.T) { + var settings apiSettings + + baseURL := "https://example.com" + + WithBaseURL(baseURL)(&settings) + + assert.Equal(t, settings.baseURL, baseURL) +} + +func TestNotionVersion(t *testing.T) { + var settings apiSettings + + notionVersion := "2021-05-19" + + WithNotionVersion(notionVersion)(&settings) + + assert.Equal(t, settings.notionVersion, notionVersion) +} + +func TestHTTPClient(t *testing.T) { + var settings apiSettings + + httpClient := &http.Client{} + + WithHTTPClient(httpClient)(&settings) + + assert.Same(t, settings.httpClient, httpClient) +} From 6453814cb3234aa9640f72bf074028a2f13ed7b9 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 23:52:16 +0800 Subject: [PATCH 26/32] ci: add github actions for release process (#12) --- .github/release-drafter.yml | 23 +++++++++++++ .github/workflows/develop.yaml | 1 + .github/workflows/draft-new-release.yaml | 37 ++++++++++++++++++++ .github/workflows/publish-new-release.yaml | 40 ++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/draft-new-release.yaml create mode 100644 .github/workflows/publish-new-release.yaml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..7f88a0f --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,23 @@ +name-template: '$RESOLVED_VERSION' +tag-template: '$RESOLVED_VERSION' +categories: + - title: '🚀 Features' + labels: + - 'feat' + - 'feature' + - 'doc' + - 'enhancement' + - 'test' + - 'refactor' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bug' + - title: '🧰 Maintenance' + labels: + - 'chore' + - 'ci' +change-template: '- $TITLE (#$NUMBER) @$AUTHOR' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +template: | + $CHANGES diff --git a/.github/workflows/develop.yaml b/.github/workflows/develop.yaml index fc2a03d..a921e85 100644 --- a/.github/workflows/develop.yaml +++ b/.github/workflows/develop.yaml @@ -3,6 +3,7 @@ on: push: branches: - develop + - main pull_request: jobs: lint: diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml new file mode 100644 index 0000000..85783ab --- /dev/null +++ b/.github/workflows/draft-new-release.yaml @@ -0,0 +1,37 @@ +name: "Draft new release" + +on: + workflow_dispatch: + inputs: + version: + description: 'The version to release' + required: true + +jobs: + draft-new-release: + name: "Draft new release" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Validate the version + uses: actions-ecosystem/action-regex-match@v2 + with: + text: ${{ github.event.inputs.version }} + regex: '^v\d+(\.\d+){2}$' + + - name: Create release branch + run: git checkout -b release/${{ github.event.inputs.version }} + + - name: Push new branch + run: git push origin release/${{ github.event.inputs.version }} + + - name: Create pull request + uses: thomaseizinger/create-pull-request@1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + head: release/${{ github.event.inputs.version }} + base: main + title: Release version ${{ github.event.inputs.version }} + reviewers: ${{ github.actor }} diff --git a/.github/workflows/publish-new-release.yaml b/.github/workflows/publish-new-release.yaml new file mode 100644 index 0000000..7076ccc --- /dev/null +++ b/.github/workflows/publish-new-release.yaml @@ -0,0 +1,40 @@ +name: "Publish new release" + +on: + pull_request: + branches: + - main + types: + - closed + +jobs: + release: + name: Publish new release + runs-on: ubuntu-latest + + if: github.event.pull_request.merged == true + + steps: + - name: Extract version from branch name + if: startsWith(github.event.pull_request.head.ref, 'release/') + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Validate the version + uses: actions-ecosystem/action-regex-match@v2 + with: + text: ${{ env.RELEASE_VERSION }} + regex: '^v\d+(\.\d+){2}$' + + - name: Create Release + id: generate_changelog + uses: release-drafter/release-drafter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.RELEASE_VERSION }} + tag: ${{ env.RELEASE_VERSION }} + name: Release ${{ env.RELEASE_VERSION }} + publish: false From 7062870e3fce6a24446126d0045c3d9029b3dcc9 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Wed, 19 May 2021 23:59:40 +0800 Subject: [PATCH 27/32] ci: rename the release name --- .github/workflows/publish-new-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-new-release.yaml b/.github/workflows/publish-new-release.yaml index 7076ccc..bc3268a 100644 --- a/.github/workflows/publish-new-release.yaml +++ b/.github/workflows/publish-new-release.yaml @@ -36,5 +36,5 @@ jobs: with: version: ${{ env.RELEASE_VERSION }} tag: ${{ env.RELEASE_VERSION }} - name: Release ${{ env.RELEASE_VERSION }} + name: ${{ env.RELEASE_VERSION }} publish: false From 0e2eed3dfc8272ca72f6b7d42c42d49a77bea4d3 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Thu, 20 May 2021 00:07:29 +0800 Subject: [PATCH 28/32] ci: change template of release body --- .github/release-drafter.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 7f88a0f..bc50698 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -17,7 +17,12 @@ categories: labels: - 'chore' - 'ci' -change-template: '- $TITLE (#$NUMBER) @$AUTHOR' +change-template: | + + # $TITLE (#$NUMBER) @$AUTHOR + + $BODY + change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. template: | $CHANGES From eac541e092edd837d788b1b058aaae29b0c74342 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Thu, 20 May 2021 00:15:38 +0800 Subject: [PATCH 29/32] ci: draft a new release by pushing a release branch --- .github/workflows/draft-new-release.yaml | 27 ++++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index 85783ab..62be56a 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -1,11 +1,9 @@ name: "Draft new release" on: - workflow_dispatch: - inputs: - version: - description: 'The version to release' - required: true + push: + branches: + - release/v*.*.* jobs: draft-new-release: @@ -14,24 +12,25 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Extract version from branch name + if: startsWith(github.event.pull_request.head.ref, 'release/') + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + - name: Validate the version uses: actions-ecosystem/action-regex-match@v2 with: - text: ${{ github.event.inputs.version }} + text: ${{ env.RELEASE_VERSION }} regex: '^v\d+(\.\d+){2}$' - - name: Create release branch - run: git checkout -b release/${{ github.event.inputs.version }} - - - name: Push new branch - run: git push origin release/${{ github.event.inputs.version }} - - name: Create pull request uses: thomaseizinger/create-pull-request@1.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - head: release/${{ github.event.inputs.version }} + head: release/${{ env.RELEASE_VERSION }} base: main - title: Release version ${{ github.event.inputs.version }} + title: Release version ${{ env.RELEASE_VERSION }} reviewers: ${{ github.actor }} From 60ca4d28df2767af5b4d97970fd1b5028ac78789 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Thu, 20 May 2021 00:29:27 +0800 Subject: [PATCH 30/32] ci: check if version is valid --- .github/workflows/draft-new-release.yaml | 4 +++- .github/workflows/publish-new-release.yaml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index 62be56a..324a8f4 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -15,17 +15,19 @@ jobs: - name: Extract version from branch name if: startsWith(github.event.pull_request.head.ref, 'release/') run: | - BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + BRANCH_NAME="${{ echo ${GITHUB_REF#refs/heads/} }}" VERSION=${BRANCH_NAME#release/} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - name: Validate the version + id: regex-match uses: actions-ecosystem/action-regex-match@v2 with: text: ${{ env.RELEASE_VERSION }} regex: '^v\d+(\.\d+){2}$' - name: Create pull request + if: ${{ steps.regex-match.outputs.match != '' }} uses: thomaseizinger/create-pull-request@1.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-new-release.yaml b/.github/workflows/publish-new-release.yaml index bc3268a..196c03b 100644 --- a/.github/workflows/publish-new-release.yaml +++ b/.github/workflows/publish-new-release.yaml @@ -23,12 +23,14 @@ jobs: echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - name: Validate the version + id: regex-match uses: actions-ecosystem/action-regex-match@v2 with: text: ${{ env.RELEASE_VERSION }} regex: '^v\d+(\.\d+){2}$' - name: Create Release + if: ${{ steps.regex-match.outputs.match != '' }} id: generate_changelog uses: release-drafter/release-drafter@v5 env: From e22d1e59f9792d86eae13241d5c45640a0ede406 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Thu, 20 May 2021 00:31:54 +0800 Subject: [PATCH 31/32] ci: fix invalid way to retrieve branch name --- .github/workflows/draft-new-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index 324a8f4..4fbe3eb 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -15,7 +15,7 @@ jobs: - name: Extract version from branch name if: startsWith(github.event.pull_request.head.ref, 'release/') run: | - BRANCH_NAME="${{ echo ${GITHUB_REF#refs/heads/} }}" + BRANCH_NAME=${GITHUB_REF#refs/heads/} VERSION=${BRANCH_NAME#release/} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV From 66c79b6bc9b13dee1864f3041715956ea7e7acd2 Mon Sep 17 00:00:00 2001 From: Pei-Ming Wu Date: Thu, 20 May 2021 00:36:00 +0800 Subject: [PATCH 32/32] ci: remove incorrect precheck in extract version step --- .github/workflows/draft-new-release.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index 4fbe3eb..6148930 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -13,7 +13,6 @@ jobs: - uses: actions/checkout@v2 - name: Extract version from branch name - if: startsWith(github.event.pull_request.head.ref, 'release/') run: | BRANCH_NAME=${GITHUB_REF#refs/heads/} VERSION=${BRANCH_NAME#release/}