sift_query_core/query/scope/
group.rs1use super::{ScopeLimits, validate_conditions, validate_predicates};
2use crate::{
3 Condition, FieldId, FieldKind, FieldRef, GroupScope, IntoScalarValue, Operator, ParamError,
4 PredicateArena, PredicatePhase, ScalarValue, SearchEntity,
5};
6use std::marker::PhantomData;
7
8pub struct GroupScopeBuilder<E: SearchEntity> {
10 conditions: Vec<Condition>,
11 limits: ScopeLimits,
12 error: Option<ParamError>,
13 _entity: PhantomData<fn() -> E>,
14}
15
16impl<E: SearchEntity> GroupScope<E> {
17 pub fn any(scopes: impl IntoIterator<Item = Self>) -> Result<Self, ParamError> {
19 Self::any_with_limits(scopes, ScopeLimits::default())
20 }
21 #[must_use]
22 pub fn new() -> GroupScopeBuilder<E> {
23 Self::builder(ScopeLimits::default())
24 }
25
26 #[must_use]
27 pub fn builder(limits: ScopeLimits) -> GroupScopeBuilder<E> {
28 GroupScopeBuilder {
29 conditions: Vec::new(),
30 limits,
31 error: None,
32 _entity: PhantomData,
33 }
34 }
35
36 pub fn all(scopes: impl IntoIterator<Item = Self>) -> Result<Self, ParamError> {
37 Self::all_with_limits(scopes, ScopeLimits::default())
38 }
39
40 pub fn all_with_limits(
41 scopes: impl IntoIterator<Item = Self>,
42 limits: ScopeLimits,
43 ) -> Result<Self, ParamError> {
44 let scopes = scopes.into_iter().collect::<Vec<_>>();
45 if scopes.is_empty() {
46 return Err(ParamError::InvalidValue {
47 field: "group scope".to_owned(),
48 reason: "scope composition must not be empty".to_owned(),
49 });
50 }
51 if scopes
52 .iter()
53 .any(|scope| scope.schema_fingerprint != E::META.schema_fingerprint)
54 {
55 return Err(ParamError::UnknownField(
56 "entity schema mismatch".to_owned(),
57 ));
58 }
59 validate_predicates(scopes.iter().map(|scope| &scope.predicate), limits)?;
60 Ok(Self {
61 predicate: PredicateArena::combine_and(scopes.into_iter().map(|scope| scope.predicate)),
62 schema_fingerprint: E::META.schema_fingerprint,
63 _entity: PhantomData,
64 })
65 }
66
67 pub fn any_with_limits(
68 scopes: impl IntoIterator<Item = Self>,
69 limits: ScopeLimits,
70 ) -> Result<Self, ParamError> {
71 let scopes = scopes.into_iter().collect::<Vec<_>>();
72 if scopes.is_empty() {
73 return Err(ParamError::InvalidValue {
74 field: "group scope".to_owned(),
75 reason: "scope composition must not be empty".to_owned(),
76 });
77 }
78 if scopes
79 .iter()
80 .any(|scope| scope.schema_fingerprint != E::META.schema_fingerprint)
81 {
82 return Err(ParamError::UnknownField(
83 "entity schema mismatch".to_owned(),
84 ));
85 }
86 validate_predicates(scopes.iter().map(|scope| &scope.predicate), limits)?;
87 Ok(Self {
88 predicate: PredicateArena::combine_or(scopes.into_iter().map(|scope| scope.predicate)),
89 schema_fingerprint: E::META.schema_fingerprint,
90 _entity: PhantomData,
91 })
92 }
93}
94
95impl<E: SearchEntity> GroupScopeBuilder<E> {
96 #[must_use]
97 pub fn eq<T: IntoScalarValue>(
98 self,
99 field: FieldRef<E, T, crate::AggregateKind>,
100 value: T,
101 ) -> Self {
102 self.push_condition(field.id(), Operator::Eq, vec![value.into_scalar_value()])
103 }
104
105 #[must_use]
106 pub fn ne<T: IntoScalarValue>(
107 self,
108 field: FieldRef<E, T, crate::AggregateKind>,
109 value: T,
110 ) -> Self {
111 self.push_condition(field.id(), Operator::Ne, vec![value.into_scalar_value()])
112 }
113
114 #[must_use]
115 pub fn gt<T: IntoScalarValue>(
116 self,
117 field: FieldRef<E, T, crate::AggregateKind>,
118 value: T,
119 ) -> Self {
120 self.push_condition(field.id(), Operator::Gt, vec![value.into_scalar_value()])
121 }
122
123 #[must_use]
124 pub fn ge<T: IntoScalarValue>(
125 self,
126 field: FieldRef<E, T, crate::AggregateKind>,
127 value: T,
128 ) -> Self {
129 self.push_condition(field.id(), Operator::Ge, vec![value.into_scalar_value()])
130 }
131
132 #[must_use]
133 pub fn lt<T: IntoScalarValue>(
134 self,
135 field: FieldRef<E, T, crate::AggregateKind>,
136 value: T,
137 ) -> Self {
138 self.push_condition(field.id(), Operator::Lt, vec![value.into_scalar_value()])
139 }
140
141 #[must_use]
142 pub fn le<T: IntoScalarValue>(
143 self,
144 field: FieldRef<E, T, crate::AggregateKind>,
145 value: T,
146 ) -> Self {
147 self.push_condition(field.id(), Operator::Le, vec![value.into_scalar_value()])
148 }
149
150 #[must_use]
151 pub fn between<T: IntoScalarValue>(
152 self,
153 field: FieldRef<E, T, crate::AggregateKind>,
154 lower: T,
155 upper: T,
156 ) -> Self {
157 self.push_condition(
158 field.id(),
159 Operator::Between,
160 vec![lower.into_scalar_value(), upper.into_scalar_value()],
161 )
162 }
163
164 #[must_use]
165 pub fn in_list<T, I>(self, field: FieldRef<E, T, crate::AggregateKind>, values: I) -> Self
166 where
167 T: IntoScalarValue,
168 I: IntoIterator<Item = T>,
169 {
170 self.push_condition(
171 field.id(),
172 Operator::InList,
173 values
174 .into_iter()
175 .map(IntoScalarValue::into_scalar_value)
176 .collect(),
177 )
178 }
179
180 fn push_condition(
181 mut self,
182 field: FieldId,
183 operator: Operator,
184 values: Vec<ScalarValue>,
185 ) -> Self {
186 if self.error.is_some() {
187 return self;
188 }
189 let Some(meta) = E::META.field(field) else {
190 self.error = Some(ParamError::UnknownField(format!("#{}", field.0)));
191 return self;
192 };
193 if meta.kind != FieldKind::Aggregate
194 || !operator.accepts_field_type(meta.value_type)
195 || values
196 .iter()
197 .any(|value| value.value_type() != operator.input_type(meta.value_type))
198 {
199 self.error = Some(ParamError::InvalidValue {
200 field: meta.public_name.to_owned(),
201 reason: "group scope requires an aggregate field with a matching type".to_owned(),
202 });
203 } else if !meta.operators.contains(operator) {
204 self.error = Some(ParamError::OperatorNotAllowed {
205 field: meta.public_name.to_owned(),
206 operator: operator.short_name(),
207 });
208 } else if !operator.accepts_arity(values.len()) {
209 self.error = Some(ParamError::WrongArity {
210 field: meta.public_name.to_owned(),
211 operator: operator.short_name(),
212 expected: operator.expected_arity(),
213 actual: values.len(),
214 });
215 } else {
216 if let Some(error) = values.iter().find_map(|value| value.validate().err()) {
217 self.error = Some(error);
218 return self;
219 }
220 self.conditions.push(Condition {
221 field,
222 operator,
223 values,
224 ignore_case: false,
225 phase: PredicatePhase::Group,
226 });
227 }
228 self
229 }
230
231 pub fn build(self) -> Result<GroupScope<E>, ParamError> {
232 if let Some(error) = self.error {
233 return Err(error);
234 }
235 if self.conditions.is_empty() {
236 return Err(ParamError::InvalidValue {
237 field: "group scope".to_owned(),
238 reason: "empty scope must use GroupScope::unrestricted() explicitly".to_owned(),
239 });
240 }
241 validate_conditions(self.conditions.iter(), self.limits)?;
242 Ok(GroupScope {
243 predicate: PredicateArena::combine_and(
244 self.conditions
245 .into_iter()
246 .map(PredicateArena::from_condition),
247 ),
248 schema_fingerprint: E::META.schema_fingerprint,
249 _entity: PhantomData,
250 })
251 }
252}