Skip to main content

couchbase_core/searchx/
facets.rs

1/*
2 *
3 *  * Copyright (c) 2025 Couchbase, Inc.
4 *  *
5 *  * Licensed under the Apache License, Version 2.0 (the "License");
6 *  * you may not use this file except in compliance with the License.
7 *  * You may obtain a copy of the License at
8 *  *
9 *  *    http://www.apache.org/licenses/LICENSE-2.0
10 *  *
11 *  * Unless required by applicable law or agreed to in writing, software
12 *  * distributed under the License is distributed on an "AS IS" BASIS,
13 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  * See the License for the specific language governing permissions and
15 *  * limitations under the License.
16 *
17 */
18
19use chrono::{DateTime, FixedOffset};
20use serde::ser::SerializeMap;
21use serde::{Serialize, Serializer};
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
24#[non_exhaustive]
25pub struct TermFacet {
26    pub field: String,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub size: Option<u64>,
29}
30impl TermFacet {
31    pub fn new(field: impl Into<String>) -> Self {
32        Self {
33            field: field.into(),
34            size: None,
35        }
36    }
37
38    pub fn size(mut self, size: impl Into<Option<u64>>) -> Self {
39        self.size = size.into();
40        self
41    }
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize)]
45#[non_exhaustive]
46pub struct NumericRange {
47    pub name: String,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub min: Option<f64>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub max: Option<f64>,
52}
53
54impl NumericRange {
55    pub fn new(name: impl Into<String>) -> Self {
56        Self {
57            name: name.into(),
58            min: None,
59            max: None,
60        }
61    }
62
63    pub fn min(mut self, min: impl Into<Option<f64>>) -> Self {
64        self.min = min.into();
65        self
66    }
67
68    pub fn max(mut self, max: impl Into<Option<f64>>) -> Self {
69        self.max = max.into();
70        self
71    }
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize)]
75#[non_exhaustive]
76pub struct NumericRangeFacet {
77    pub field: String,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub size: Option<u64>,
80    pub numeric_ranges: Vec<NumericRange>,
81}
82
83impl NumericRangeFacet {
84    pub fn new(field: impl Into<String>) -> Self {
85        Self {
86            field: field.into(),
87            size: None,
88            numeric_ranges: Vec::new(),
89        }
90    }
91
92    pub fn with_numeric_ranges(
93        field: impl Into<String>,
94        numeric_ranges: impl Into<Vec<NumericRange>>,
95    ) -> Self {
96        Self {
97            field: field.into(),
98            size: None,
99            numeric_ranges: numeric_ranges.into(),
100        }
101    }
102
103    pub fn size(mut self, size: impl Into<Option<u64>>) -> Self {
104        self.size = size.into();
105        self
106    }
107
108    pub fn add_numeric_range(mut self, range: NumericRange) -> Self {
109        self.numeric_ranges.push(range);
110        self
111    }
112}
113
114#[derive(Debug, Clone, PartialEq)]
115#[non_exhaustive]
116pub struct DateRange {
117    pub name: String,
118    pub start: Option<DateTime<FixedOffset>>,
119    pub end: Option<DateTime<FixedOffset>>,
120}
121
122impl DateRange {
123    pub fn new(name: impl Into<String>) -> Self {
124        Self {
125            name: name.into(),
126            start: None,
127            end: None,
128        }
129    }
130
131    pub fn start(mut self, start: impl Into<Option<DateTime<FixedOffset>>>) -> Self {
132        self.start = start.into();
133        self
134    }
135
136    pub fn end(mut self, end: impl Into<Option<DateTime<FixedOffset>>>) -> Self {
137        self.end = end.into();
138        self
139    }
140}
141
142impl Serialize for DateRange {
143    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144    where
145        S: Serializer,
146    {
147        let mut map = serializer.serialize_map(None)?;
148
149        map.serialize_entry("name", &self.name)?;
150        if let Some(start) = &self.start {
151            map.serialize_entry("start", &start.to_rfc3339())?;
152        }
153        if let Some(end) = &self.end {
154            map.serialize_entry("end", &end.to_rfc3339())?;
155        }
156
157        map.end()
158    }
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize)]
162#[non_exhaustive]
163pub struct DateRangeFacet {
164    pub field: String,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub size: Option<u64>,
167    pub date_ranges: Vec<DateRange>,
168}
169
170impl DateRangeFacet {
171    pub fn new(field: impl Into<String>) -> Self {
172        Self {
173            field: field.into(),
174            size: None,
175            date_ranges: Vec::new(),
176        }
177    }
178
179    pub fn with_date_ranges(
180        field: impl Into<String>,
181        date_ranges: impl Into<Vec<DateRange>>,
182    ) -> Self {
183        Self {
184            field: field.into(),
185            size: None,
186            date_ranges: date_ranges.into(),
187        }
188    }
189
190    pub fn size(mut self, size: impl Into<Option<u64>>) -> Self {
191        self.size = size.into();
192        self
193    }
194
195    pub fn add_date_range(mut self, range: DateRange) -> Self {
196        self.date_ranges.push(range);
197        self
198    }
199}
200
201#[derive(Debug, Clone, PartialEq, Serialize)]
202#[serde(untagged)]
203#[non_exhaustive]
204pub enum Facet {
205    Term(TermFacet),
206    NumericRange(NumericRangeFacet),
207    DateRange(DateRangeFacet),
208}