Repository: jomei/notionapi
Pull request #14 (2021-08-08 08:24:47)

Filter reworked


Patch:
diff --git a/database.go b/database.go
index fcc5a4b..f30de36 100644
--- a/database.go
+++ b/database.go
@@ -93,10 +93,31 @@ type DatabaseListResponse struct {
 }
 
 type DatabaseQueryRequest struct {
-	Filter      Filter       `json:"filter,omitempty"`
-	Sorts       []SortObject `json:"sorts"`
-	StartCursor Cursor       `json:"start_cursor,omitempty"`
-	PageSize    int          `json:"page_size,omitempty"`
+	PropertyFilter *PropertyFilter
+	CompoundFilter *CompoundFilter
+	Sorts          []SortObject `json:"sorts,omitempty"`
+	StartCursor    Cursor       `json:"start_cursor,omitempty"`
+	PageSize       int          `json:"page_size,omitempty"`
+}
+
+func (qr *DatabaseQueryRequest) MarshalJSON() ([]byte, error) {
+	var filter interface{}
+	if qr.PropertyFilter != nil {
+		filter = qr.PropertyFilter
+	} else {
+		filter = qr.CompoundFilter
+	}
+	return json.Marshal(struct {
+		Sorts       []SortObject `json:"sorts,omitempty"`
+		StartCursor Cursor       `json:"start_cursor,omitempty"`
+		PageSize    int          `json:"page_size,omitempty"`
+		Filter      interface{}  `json:"filter"`
+	}{
+		Sorts:       qr.Sorts,
+		StartCursor: qr.StartCursor,
+		PageSize:    qr.PageSize,
+		Filter:      filter,
+	})
 }
 
 type DatabaseQueryResponse struct {
diff --git a/filter.go b/filter.go
index 3ea17ed..80ec00a 100644
--- a/filter.go
+++ b/filter.go
@@ -1,25 +1,104 @@
 package notionapi
 
-import "time"
-
 type FilterOperator string
 
-type Filter interface{}
-
-type CompoundFilter map[FilterOperator]Filter
+type CompoundFilter map[FilterOperator][]PropertyFilter
 
 type Condition string
 
 type PropertyFilter struct {
-	Property    PropertyType                    `json:"property"`
-	Text        map[Condition]string            `json:"text,omitempty"`
-	Number      map[Condition]float64           `json:"number,omitempty"`
-	Checkbox    map[Condition]bool              `json:"checkbox,omitempty"`
-	Select      map[Condition]interface{}       `json:"select,omitempty"`
-	MultiSelect map[Condition]interface{}       `json:"multi_select,omitempty"`
-	Date        map[Condition]time.Time         `json:"date,omitempty"`
-	People      map[Condition]interface{}       `json:"people,omitempty"`
-	Files       map[Condition]bool              `json:"files,omitempty"`
-	Relation    map[Condition]interface{}       `json:"relation,omitempty"`
-	Formula     map[PropertyType]PropertyFilter `json:"formula,omitempty"`
+	Property    string                      `json:"property"`
+	Text        *TextFilterCondition        `json:"text,omitempty"`
+	Number      *NumberFilterCondition      `json:"number,omitempty"`
+	Checkbox    *CheckboxFilterCondition    `json:"checkbox,omitempty"`
+	Select      *SelectFilterCondition      `json:"select,omitempty"`
+	MultiSelect *MultiSelectFilterCondition `json:"multi_select,omitempty"`
+	Date        *DateFilterCondition        `json:"date,omitempty"`
+	People      *PeopleFilterCondition      `json:"people,omitempty"`
+	Files       *FilesFilterCondition       `json:"files,omitempty"`
+	Relation    *RelationFilterCondition    `json:"relation,omitempty"`
+	Formula     *FormulaFilterCondition     `json:"formula,omitempty"`
+}
+
+type TextFilterCondition struct {
+	Equals         string `json:"equals,omitempty"`
+	DoesNotEqual   string `json:"does_not_equal,omitempty"`
+	Contains       string `json:"contains,omitempty"`
+	DoesNotContain string `json:"does_not_contain,omitempty"`
+	StartsWith     string `json:"starts_with,omitempty"`
+	EndsWith       string `json:"ends_with,omitempty"`
+	IsEmpty        bool   `json:"is_empty,omitempty"`
+	IsNotEmpty     bool   `json:"is_not_empty,omitempty"`
+}
+
+type NumberFilterCondition struct {
+	Equals               float64 `json:"equals,omitempty"`
+	DoesNotEqual         float64 `json:"does_not_equal,omitempty"`
+	GreaterThan          float64 `json:"greater_than,omitempty"`
+	LessThan             float64 `json:"less_than,omitempty"`
+	GreaterThanOrEqualTo float64 `json:"greater_than_or_equal_to"`
+	LessThanOrEqualTo    float64 `json:"less_than_or_equal_to"`
+	IsEmpty              bool    `json:"is_empty,omitempty"`
+	IsNotEmpty           bool    `json:"is_not_empty,omitempty"`
+}
+
+type CheckboxFilterCondition struct {
+	Equals       bool `json:"equals,omitempty"`
+	DoesNotEqual bool `json:"does_not_equal,omitempty"`
+}
+
+type SelectFilterCondition 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"`
+}
+
+type MultiSelectFilterCondition 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"`
+}
+
+type DateFilterCondition struct {
+	Equals     *Date     `json:"equals,omitempty"`
+	Before     *Date     `json:"before,omitempty"`
+	After      *Date     `json:"after,omitempty"`
+	OnOrBefore *Date     `json:"on_or_before,omitempty"`
+	OnOrAfter  *Date     `json:"on_or_after,omitempty"`
+	PastWeek   *struct{} `json:"past_week,omitempty"`
+	PastMonth  *struct{} `json:"past_month,omitempty"`
+	PastYear   *struct{} `json:"past_year,omitempty"`
+	NextWeek   *struct{} `json:"next_week,omitempty"`
+	NextMonth  *struct{} `json:"next_month,omitempty"`
+	NextYear   *struct{} `json:"next_year,omitempty"`
+	IsEmpty    bool      `json:"is_empty,omitempty"`
+	IsNotEmpty bool      `json:"is_not_empty,omitempty"`
+}
+
+type PeopleFilterCondition 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"`
+}
+
+type FilesFilterCondition struct {
+	IsEmpty    bool `json:"is_empty,omitempty"`
+	IsNotEmpty bool `json:"is_not_empty,omitempty"`
+}
+
+type RelationFilterCondition 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"`
+}
+
+type FormulaFilterCondition struct {
+	Text     *TextFilterCondition     `json:"text,omitempty"`
+	Checkbox *CheckboxFilterCondition `json:"checkbox,omitempty"`
+	Number   *NumberFilterCondition   `json:"number,omitempty"`
+	Date     *DateFilterCondition     `json:"date,omitempty"`
 }
diff --git a/object.go b/object.go
index f25d08e..100c399 100644
--- a/object.go
+++ b/object.go
@@ -1,5 +1,7 @@
 package notionapi
 
+import "time"
+
 type ObjectType string
 
 func (ot ObjectType) String() string {
@@ -66,3 +68,13 @@ type Cursor string
 func (c Cursor) String() string {
 	return string(c)
 }
+
+type Date time.Time
+
+func (d *Date) String() string {
+	return time.Time(*d).Format(time.RFC3339)
+}
+
+func (d *Date) MarshalText() ([]byte, error) {
+	return []byte(d.String()), nil
+}
diff --git a/search.go b/search.go
index ee767a3..71ae413 100644
--- a/search.go
+++ b/search.go
@@ -34,7 +34,7 @@ func (sc *SearchClient) Do(ctx context.Context, request *SearchRequest) (*Search
 type SearchRequest struct {
 	Query       string      `json:"query,omitempty"`
 	Sort        *SortObject `json:"sort,omitempty"`
-	Filter      Filter      `json:"filter,omitempty"`
+	Filter      interface{} `json:"filter,omitempty"`
 	StartCursor Cursor      `json:"start_cursor,omitempty"`
 	PageSize    int         `json:"page_size"`
 }
diff --git a/sort.go b/sort.go
index 5133c5f..1334f8c 100644
--- a/sort.go
+++ b/sort.go
@@ -6,6 +6,6 @@ type TimestampType string
 
 type SortObject struct {
 	Property  string        `json:"property,omitempty"`
-	Timestamp TimestampType `json:"timestamp"`
-	Direction SortOrder     `json:"direction"`
+	Timestamp TimestampType `json:"timestamp,omitempty"`
+	Direction SortOrder     `json:"direction,omitempty"`
 }


Test patch:
diff --git a/database_test.go b/database_test.go
index 8caa19f..a8108c8 100644
--- a/database_test.go
+++ b/database_test.go
@@ -166,10 +166,10 @@ func TestDatabaseClient(t *testing.T) {
 				filePath:   "testdata/database_query.json",
 				statusCode: http.StatusOK,
 				request: &notionapi.DatabaseQueryRequest{
-					Filter: &notionapi.PropertyFilter{
+					PropertyFilter: &notionapi.PropertyFilter{
 						Property: "Name",
-						Text: map[notionapi.Condition]string{
-							notionapi.ConditionContains: "Hel",
+						Text: &notionapi.TextFilterCondition{
+							Contains: "Hel",
 						},
 					},
 				},
@@ -213,3 +213,96 @@ func TestDatabaseClient(t *testing.T) {
 		}
 	})
 }
+
+func TestDatabaseQueryRequest_MarshalJSON(t *testing.T) {
+	timeObj, err := time.Parse(time.RFC3339, "2021-05-10T02:43:42Z")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	dateObj := notionapi.Date(timeObj)
+	tests := []struct {
+		name    string
+		req     *notionapi.DatabaseQueryRequest
+		want    []byte
+		wantErr bool
+	}{
+		{
+			name: "with property filter without sort",
+			req: &notionapi.DatabaseQueryRequest{
+				PropertyFilter: &notionapi.PropertyFilter{
+					Property: "Status",
+					Select: &notionapi.SelectFilterCondition{
+						Equals: "Reading",
+					},
+				},
+			},
+			want: []byte(`{"filter":{"property":"Status","select":{"equals":"Reading"}}}`),
+		},
+		{
+			name: "with property filter with sort",
+			req: &notionapi.DatabaseQueryRequest{
+				PropertyFilter: &notionapi.PropertyFilter{
+					Property: "Status",
+					Select: &notionapi.SelectFilterCondition{
+						Equals: "Reading",
+					},
+				},
+				Sorts: []notionapi.SortObject{
+					{
+						Property:  "Score /5",
+						Direction: notionapi.SortOrderASC,
+					},
+				},
+			},
+			want: []byte(`{"sorts":[{"property":"Score /5","direction":"ascending"}],"filter":{"property":"Status","select":{"equals":"Reading"}}}`),
+		},
+		{
+			name: "compound filter",
+			req: &notionapi.DatabaseQueryRequest{
+				CompoundFilter: &notionapi.CompoundFilter{
+					notionapi.FilterOperatorOR: []notionapi.PropertyFilter{
+						{
+							Property: "Status",
+							Select: &notionapi.SelectFilterCondition{
+								Equals: "Reading",
+							},
+						},
+						{
+							Property: "Publisher",
+							Select: &notionapi.SelectFilterCondition{
+								Equals: "NYT",
+							},
+						},
+					},
+				},
+			},
+			want: []byte(`{"filter":{"or":[{"property":"Status","select":{"equals":"Reading"}},{"property":"Publisher","select":{"equals":"NYT"}}]}}`),
+		},
+		{
+			name: "date filter",
+			req: &notionapi.DatabaseQueryRequest{
+				PropertyFilter: &notionapi.PropertyFilter{
+					Property: "created_at",
+					Date: &notionapi.DateFilterCondition{
+						Equals:   &dateObj,
+						PastWeek: &struct{}{},
+					},
+				},
+			},
+			want: []byte(`{"filter":{"property":"created_at","date":{"equals":"2021-05-10T02:43:42Z","past_week":{}}}}`),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.req.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("MarshalJSON() got = %s, want %s", got, tt.want)
+			}
+		})
+	}
+}