notion_sdk/search/
mod.rs

1//!
2//! # Notion Search
3//!
4mod api;
5
6use crate::pages::id::PageId;
7use crate::pagination::{Pageable, Paging, PagingCursor};
8use crate::user::id::UserId;
9use chrono::{DateTime, Utc};
10use serde::ser::SerializeMap;
11use serde::{Serialize, Serializer};
12use serde_json::Number;
13
14#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
15#[serde(rename_all = "snake_case")]
16pub enum SortDirection {
17    Ascending,
18    Descending,
19}
20
21#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
22#[serde(rename_all = "snake_case")]
23pub enum SortTimestamp {
24    LastEditedTime,
25}
26
27#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
28#[serde(rename_all = "snake_case")]
29pub enum FilterValue {
30    Page,
31    Database,
32}
33
34#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
35#[serde(rename_all = "snake_case")]
36pub enum FilterProperty {
37    Object,
38}
39
40#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
41pub struct Sort {
42    /// The name of the timestamp to sort against.
43    timestamp: SortTimestamp,
44    direction: SortDirection,
45}
46
47#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
48pub struct Filter {
49    property: FilterProperty,
50    value: FilterValue,
51}
52
53#[derive(Serialize, Debug, Eq, PartialEq, Default)]
54pub struct SearchRequest {
55    #[serde(skip_serializing_if = "Option::is_none")]
56    query: Option<String>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    sort: Option<Sort>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    filter: Option<Filter>,
61    #[serde(flatten)]
62    paging: Option<Paging>,
63}
64
65#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
66#[serde(rename_all = "snake_case")]
67pub enum TextCondition {
68    Equals(String),
69    DoesNotEqual(String),
70    Contains(String),
71    DoesNotContain(String),
72    StartsWith(String),
73    EndsWith(String),
74    #[serde(serialize_with = "serialize_to_true")]
75    IsEmpty,
76    #[serde(serialize_with = "serialize_to_true")]
77    IsNotEmpty,
78}
79
80fn serialize_to_true<S>(serializer: S) -> Result<S::Ok, S::Error>
81where
82    S: Serializer,
83{
84    serializer.serialize_bool(true)
85}
86
87fn serialize_to_empty_object<S>(serializer: S) -> Result<S::Ok, S::Error>
88where
89    S: Serializer,
90{
91    // Todo: there has to be a better way?
92    serializer.serialize_map(Some(0))?.end()
93}
94
95#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
96#[serde(rename_all = "snake_case")]
97pub enum NumberCondition {
98    Equals(Number),
99    DoesNotEqual(Number),
100    GreaterThan(Number),
101    LessThan(Number),
102    GreaterThanOrEqualTo(Number),
103    LessThanOrEqualTo(Number),
104    #[serde(serialize_with = "serialize_to_true")]
105    IsEmpty,
106    #[serde(serialize_with = "serialize_to_true")]
107    IsNotEmpty,
108}
109
110#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
111#[serde(rename_all = "snake_case")]
112pub enum CheckboxCondition {
113    Equals(bool),
114    DoesNotEqual(bool),
115}
116
117#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
118#[serde(rename_all = "snake_case")]
119pub enum SelectCondition {
120    /// Only return pages where the page property value matches the provided value exactly.
121    Equals(String),
122    /// Only return pages where the page property value does not match the provided value exactly.
123    DoesNotEqual(String),
124    /// Only return pages where the page property value is empty.
125    #[serde(serialize_with = "serialize_to_true")]
126    IsEmpty,
127    /// Only return pages where the page property value is present.
128    #[serde(serialize_with = "serialize_to_true")]
129    IsNotEmpty,
130}
131
132#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
133#[serde(rename_all = "snake_case")]
134pub enum MultiSelectCondition {
135    /// Only return pages where the page property value contains the provided value.
136    Contains(String),
137    /// Only return pages where the page property value does not contain the provided value.
138    DoesNotContain(String),
139    /// Only return pages where the page property value is empty.
140    #[serde(serialize_with = "serialize_to_true")]
141    IsEmpty,
142    /// Only return pages where the page property value is present.
143    #[serde(serialize_with = "serialize_to_true")]
144    IsNotEmpty,
145}
146
147#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
148#[serde(rename_all = "snake_case")]
149pub enum DateCondition {
150    /// Only return pages where the page property value matches the provided date exactly.
151    /// Note that the comparison is done against the date.
152    /// Any time information sent will be ignored.
153    Equals(DateTime<Utc>),
154    /// Only return pages where the page property value is before the provided date.
155    /// Note that the comparison is done against the date.
156    /// Any time information sent will be ignored.
157    Before(DateTime<Utc>),
158    /// Only return pages where the page property value is after the provided date.
159    /// Note that the comparison is done against the date.
160    /// Any time information sent will be ignored.
161    After(DateTime<Utc>),
162    /// Only return pages where the page property value is on or before the provided date.
163    /// Note that the comparison is done against the date.
164    /// Any time information sent will be ignored.
165    OnOrBefore(DateTime<Utc>),
166    /// Only return pages where the page property value is on or after the provided date.
167    /// Note that the comparison is done against the date.
168    /// Any time information sent will be ignored.
169    OnOrAfter(DateTime<Utc>),
170    /// Only return pages where the page property value is empty.
171    #[serde(serialize_with = "serialize_to_true")]
172    IsEmpty,
173    /// Only return pages where the page property value is present.
174    #[serde(serialize_with = "serialize_to_true")]
175    IsNotEmpty,
176    /// Only return pages where the page property value is within the past week.
177    #[serde(serialize_with = "serialize_to_empty_object")]
178    PastWeek,
179    /// Only return pages where the page property value is within the past month.
180    #[serde(serialize_with = "serialize_to_empty_object")]
181    PastMonth,
182    /// Only return pages where the page property value is within the past year.
183    #[serde(serialize_with = "serialize_to_empty_object")]
184    PastYear,
185    /// Only return pages where the page property value is within the next week.
186    #[serde(serialize_with = "serialize_to_empty_object")]
187    NextWeek,
188    /// Only return pages where the page property value is within the next month.
189    #[serde(serialize_with = "serialize_to_empty_object")]
190    NextMonth,
191    /// Only return pages where the page property value is within the next year.
192    #[serde(serialize_with = "serialize_to_empty_object")]
193    NextYear,
194}
195
196#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
197#[serde(rename_all = "snake_case")]
198pub enum PeopleCondition {
199    Contains(UserId),
200    /// Only return pages where the page property value does not contain the provided value.
201    DoesNotContain(UserId),
202    /// Only return pages where the page property value is empty.
203    #[serde(serialize_with = "serialize_to_true")]
204    IsEmpty,
205    /// Only return pages where the page property value is present.
206    #[serde(serialize_with = "serialize_to_true")]
207    IsNotEmpty,
208}
209
210#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
211#[serde(rename_all = "snake_case")]
212pub enum FilesCondition {
213    /// Only return pages where the page property value is empty.
214    #[serde(serialize_with = "serialize_to_true")]
215    IsEmpty,
216    /// Only return pages where the page property value is present.
217    #[serde(serialize_with = "serialize_to_true")]
218    IsNotEmpty,
219}
220
221#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
222#[serde(rename_all = "snake_case")]
223pub enum RelationCondition {
224    /// Only return pages where the page property value contains the provided value.
225    Contains(PageId),
226    /// Only return pages where the page property value does not contain the provided value.
227    DoesNotContain(PageId),
228    /// Only return pages where the page property value is empty.
229    #[serde(serialize_with = "serialize_to_true")]
230    IsEmpty,
231    /// Only return pages where the page property value is present.
232    #[serde(serialize_with = "serialize_to_true")]
233    IsNotEmpty,
234}
235
236#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
237#[serde(rename_all = "snake_case")]
238pub enum FormulaCondition {
239    /// Only return pages where the result type of the page property formula is "text"
240    /// and the provided text filter condition matches the formula's value.
241    Text(TextCondition),
242    /// Only return pages where the result type of the page property formula is "number"
243    /// and the provided number filter condition matches the formula's value.
244    Number(NumberCondition),
245    /// Only return pages where the result type of the page property formula is "checkbox"
246    /// and the provided checkbox filter condition matches the formula's value.
247    Checkbox(CheckboxCondition),
248    /// Only return pages where the result type of the page property formula is "date"
249    /// and the provided date filter condition matches the formula's value.
250    Date(DateCondition),
251}
252
253#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
254#[serde(rename_all = "snake_case")]
255pub enum PropertyCondition {
256    RichText(TextCondition),
257    Number(NumberCondition),
258    Checkbox(CheckboxCondition),
259    Select(SelectCondition),
260    MultiSelect(MultiSelectCondition),
261    Date(DateCondition),
262    People(PeopleCondition),
263    Files(FilesCondition),
264    Relation(RelationCondition),
265    Formula(FormulaCondition),
266}
267
268#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
269#[serde(untagged)]
270pub enum FilterCondition {
271    Property {
272        property: String,
273        #[serde(flatten)]
274        condition: PropertyCondition,
275    },
276    /// Returns pages when **all** of the filters inside the provided vector match.
277    And { and: Vec<FilterCondition> },
278    /// Returns pages when **any** of the filters inside the provided vector match.
279    Or { or: Vec<FilterCondition> },
280}
281
282#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)]
283#[serde(rename_all = "snake_case")]
284pub enum DatabaseSortTimestamp {
285    CreatedTime,
286    LastEditedTime,
287}
288
289#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
290pub struct DatabaseSort {
291    // Todo: Should property and timestamp be mutually exclusive? (i.e a flattened enum?)
292    //  the documentation is not clear:
293    //  https://developers.notion.com/reference/post-database-query#post-database-query-sort
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub property: Option<String>,
296    /// The name of the timestamp to sort against.
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub timestamp: Option<DatabaseSortTimestamp>,
299    /// ascending or descending
300    pub direction: SortDirection,
301}
302
303#[derive(Serialize, Debug, Eq, PartialEq, Default, Clone)]
304pub struct DatabaseQuery {
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub sorts: Option<Vec<DatabaseSort>>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub filter: Option<FilterCondition>,
309    #[serde(flatten)]
310    pub paging: Option<Paging>,
311}
312
313impl Pageable for DatabaseQuery {
314    fn start_from(self, starting_point: Option<PagingCursor>) -> Self {
315        DatabaseQuery {
316            paging: Some(Paging {
317                start_cursor: starting_point,
318                page_size: self.paging.and_then(|p| p.page_size),
319            }),
320            ..self
321        }
322    }
323}
324
325#[derive(Debug, Eq, PartialEq)]
326pub enum NotionSearch {
327    /// When supplied, limits which pages are returned by comparing the query to the page title.
328    Query(String),
329    /// When supplied, sorts the results based on the provided criteria.
330    ///
331    /// Limitation: Currently only a single sort is allowed and is limited to `last_edited_time`
332    Sort {
333        timestamp: SortTimestamp,
334        direction: SortDirection,
335    },
336    /// When supplied, filters the results based on the provided criteria.
337    ///
338    /// Limitation: Currently the only filter allowed is `object` which will filter by type of object (either page or database)
339    Filter {
340        /// The name of the property to filter by.
341        /// Currently the only property you can filter by is the `object` type.
342        property: FilterProperty,
343        /// The value of the property to filter the results by.
344        /// Possible values for object type include `page` or `database`.
345        value: FilterValue,
346    },
347}
348
349impl NotionSearch {
350    pub fn filter_by_databases() -> Self {
351        Self::Filter {
352            property: FilterProperty::Object,
353            value: FilterValue::Database,
354        }
355    }
356}
357
358impl From<NotionSearch> for SearchRequest {
359    fn from(search: NotionSearch) -> Self {
360        match search {
361            NotionSearch::Query(query) => SearchRequest {
362                query: Some(query),
363                ..Default::default()
364            },
365            NotionSearch::Sort {
366                direction,
367                timestamp,
368            } => SearchRequest {
369                sort: Some(Sort {
370                    timestamp,
371                    direction,
372                }),
373                ..Default::default()
374            },
375            NotionSearch::Filter { property, value } => SearchRequest {
376                filter: Some(Filter { property, value }),
377                ..Default::default()
378            },
379        }
380    }
381}