1use super::{ScopeLimits, validate_conditions, validate_predicates};
2use crate::{
3 Condition, CustomOperator, FieldId, FieldKind, FieldRef, IntoScalarValue, Operator, ParamError,
4 PredicateArena, PredicatePhase, RowPhaseKind, ScalarValue, SearchEntity, ServerScope,
5};
6use std::marker::PhantomData;
7
8pub struct ServerScopeBuilder<E: SearchEntity> {
9 conditions: Vec<Condition>,
10 limits: ScopeLimits,
11 error: Option<ParamError>,
12 _entity: PhantomData<fn() -> E>,
13}
14
15impl<E: SearchEntity> ServerScope<E> {
16 #[must_use]
17 pub fn new() -> ServerScopeBuilder<E> {
18 Self::builder(ScopeLimits::default())
19 }
20
21 #[must_use]
22 pub fn builder(limits: ScopeLimits) -> ServerScopeBuilder<E> {
23 ServerScopeBuilder {
24 conditions: Vec::new(),
25 limits,
26 error: None,
27 _entity: PhantomData,
28 }
29 }
30
31 pub fn all(scopes: impl IntoIterator<Item = Self>) -> Result<Self, ParamError> {
32 Self::all_with_limits(scopes, ScopeLimits::default())
33 }
34
35 pub fn any(scopes: impl IntoIterator<Item = Self>) -> Result<Self, ParamError> {
36 Self::any_with_limits(scopes, ScopeLimits::default())
37 }
38
39 pub fn all_with_limits(
40 scopes: impl IntoIterator<Item = Self>,
41 limits: ScopeLimits,
42 ) -> Result<Self, ParamError> {
43 Self::combine(scopes, true, limits)
44 }
45
46 pub fn any_with_limits(
47 scopes: impl IntoIterator<Item = Self>,
48 limits: ScopeLimits,
49 ) -> Result<Self, ParamError> {
50 Self::combine(scopes, false, limits)
51 }
52
53 fn combine(
54 scopes: impl IntoIterator<Item = Self>,
55 and: bool,
56 limits: ScopeLimits,
57 ) -> Result<Self, ParamError> {
58 let scopes = scopes.into_iter().collect::<Vec<_>>();
59 if scopes.is_empty() {
60 return Err(ParamError::InvalidValue {
61 field: "server scope".to_owned(),
62 reason: "scope composition must not be empty".to_owned(),
63 });
64 }
65 if scopes
66 .iter()
67 .any(|scope| scope.schema_fingerprint != E::META.schema_fingerprint)
68 {
69 return Err(ParamError::UnknownField(
70 "entity schema mismatch".to_owned(),
71 ));
72 }
73 validate_predicates(scopes.iter().map(|scope| &scope.predicate), limits)?;
74 let field_access = scopes
75 .iter()
76 .skip(1)
77 .fold(scopes[0].field_access.clone(), |access, scope| {
78 access.intersect(&scope.field_access)
79 });
80 let predicates = scopes.into_iter().map(|scope| scope.predicate);
81 Ok(Self {
82 predicate: if and {
83 PredicateArena::combine_and(predicates)
84 } else {
85 PredicateArena::combine_or(predicates)
86 },
87 schema_fingerprint: E::META.schema_fingerprint,
88 field_access,
89 _entity: PhantomData,
90 })
91 }
92}
93
94impl<E: SearchEntity> ServerScopeBuilder<E> {
95 #[must_use]
96 pub fn eq<T: IntoScalarValue, K: RowPhaseKind>(
97 self,
98 field: FieldRef<E, T, K>,
99 value: T,
100 ) -> Self {
101 self.push_condition(
102 field.id(),
103 Operator::Eq,
104 vec![value.into_scalar_value()],
105 false,
106 )
107 }
108
109 #[must_use]
110 pub fn ne<T: IntoScalarValue, K: RowPhaseKind>(
111 self,
112 field: FieldRef<E, T, K>,
113 value: T,
114 ) -> Self {
115 self.push_condition(
116 field.id(),
117 Operator::Ne,
118 vec![value.into_scalar_value()],
119 false,
120 )
121 }
122
123 #[must_use]
124 pub fn gt<T: IntoScalarValue, K: RowPhaseKind>(
125 self,
126 field: FieldRef<E, T, K>,
127 value: T,
128 ) -> Self {
129 self.push_condition(
130 field.id(),
131 Operator::Gt,
132 vec![value.into_scalar_value()],
133 false,
134 )
135 }
136
137 #[must_use]
138 pub fn ge<T: IntoScalarValue, K: RowPhaseKind>(
139 self,
140 field: FieldRef<E, T, K>,
141 value: T,
142 ) -> Self {
143 self.push_condition(
144 field.id(),
145 Operator::Ge,
146 vec![value.into_scalar_value()],
147 false,
148 )
149 }
150
151 #[must_use]
152 pub fn lt<T: IntoScalarValue, K: RowPhaseKind>(
153 self,
154 field: FieldRef<E, T, K>,
155 value: T,
156 ) -> Self {
157 self.push_condition(
158 field.id(),
159 Operator::Lt,
160 vec![value.into_scalar_value()],
161 false,
162 )
163 }
164
165 #[must_use]
166 pub fn le<T: IntoScalarValue, K: RowPhaseKind>(
167 self,
168 field: FieldRef<E, T, K>,
169 value: T,
170 ) -> Self {
171 self.push_condition(
172 field.id(),
173 Operator::Le,
174 vec![value.into_scalar_value()],
175 false,
176 )
177 }
178
179 #[must_use]
180 pub fn between<T: IntoScalarValue, K: RowPhaseKind>(
181 self,
182 field: FieldRef<E, T, K>,
183 lower: T,
184 upper: T,
185 ) -> Self {
186 self.push_condition(
187 field.id(),
188 Operator::Between,
189 vec![lower.into_scalar_value(), upper.into_scalar_value()],
190 false,
191 )
192 }
193
194 #[must_use]
195 pub fn in_list<T, K, I>(self, field: FieldRef<E, T, K>, values: I) -> Self
196 where
197 T: IntoScalarValue,
198 K: RowPhaseKind,
199 I: IntoIterator<Item = T>,
200 {
201 self.push_condition(
202 field.id(),
203 Operator::InList,
204 values
205 .into_iter()
206 .map(IntoScalarValue::into_scalar_value)
207 .collect(),
208 false,
209 )
210 }
211
212 #[must_use]
213 pub fn is_null<T, K: RowPhaseKind>(self, field: FieldRef<E, T, K>) -> Self {
214 self.push_condition(field.id(), Operator::IsNull, Vec::new(), false)
215 }
216
217 #[must_use]
218 pub fn is_not_null<T, K: RowPhaseKind>(self, field: FieldRef<E, T, K>) -> Self {
219 self.push_condition(field.id(), Operator::NotNull, Vec::new(), false)
220 }
221
222 #[must_use]
223 pub fn starts_with<K: RowPhaseKind>(
224 self,
225 field: FieldRef<E, String, K>,
226 value: impl Into<String>,
227 ) -> Self {
228 self.push_condition(
229 field.id(),
230 Operator::StartWith,
231 vec![ScalarValue::Text(value.into())],
232 false,
233 )
234 }
235
236 #[must_use]
237 pub fn contains<K: RowPhaseKind>(
238 self,
239 field: FieldRef<E, String, K>,
240 value: impl Into<String>,
241 ) -> Self {
242 self.push_condition(
243 field.id(),
244 Operator::Contain,
245 vec![ScalarValue::Text(value.into())],
246 false,
247 )
248 }
249
250 #[must_use]
251 pub fn contains_ignore_case<K: RowPhaseKind>(
252 self,
253 field: FieldRef<E, String, K>,
254 value: impl Into<String>,
255 ) -> Self {
256 self.push_condition(
257 field.id(),
258 Operator::Contain,
259 vec![ScalarValue::Text(value.into())],
260 true,
261 )
262 }
263
264 #[must_use]
265 pub fn custom<T, K, I>(
266 self,
267 field: FieldRef<E, T, K>,
268 operator: CustomOperator,
269 values: I,
270 ) -> Self
271 where
272 T: IntoScalarValue,
273 K: RowPhaseKind,
274 I: IntoIterator<Item = T>,
275 {
276 self.custom_values(field, operator, values)
277 }
278
279 #[must_use]
281 pub fn custom_values<T, K: RowPhaseKind, V: IntoScalarValue>(
282 self,
283 field: FieldRef<E, T, K>,
284 operator: CustomOperator,
285 values: impl IntoIterator<Item = V>,
286 ) -> Self {
287 self.push_condition(
288 field.id(),
289 Operator::Custom(operator),
290 values
291 .into_iter()
292 .map(IntoScalarValue::into_scalar_value)
293 .collect(),
294 false,
295 )
296 }
297
298 #[must_use]
301 pub fn exists<T, K: RowPhaseKind, V: IntoScalarValue>(
302 self,
303 field: FieldRef<E, T, K>,
304 operator: CustomOperator,
305 values: impl IntoIterator<Item = V>,
306 ) -> Self {
307 if !operator.is_exists() {
308 return self.push_error(ParamError::InvalidValue {
309 field: "exists".to_owned(),
310 reason: "operator must be declared with CustomOperator::exists".to_owned(),
311 });
312 }
313 self.custom_values(field, operator, values)
314 }
315
316 #[must_use]
319 pub fn not_exists<T, K: RowPhaseKind, V: IntoScalarValue>(
320 self,
321 field: FieldRef<E, T, K>,
322 operator: CustomOperator,
323 values: impl IntoIterator<Item = V>,
324 ) -> Self {
325 if !operator.is_not_exists() {
326 return self.push_error(ParamError::InvalidValue {
327 field: "not_exists".to_owned(),
328 reason: "operator must be declared with CustomOperator::not_exists".to_owned(),
329 });
330 }
331 self.custom_values(field, operator, values)
332 }
333
334 fn push_error(mut self, error: ParamError) -> Self {
335 if self.error.is_none() {
336 self.error = Some(error);
337 }
338 self
339 }
340
341 fn push_condition(
342 mut self,
343 field: FieldId,
344 operator: Operator,
345 values: Vec<ScalarValue>,
346 ignore_case: bool,
347 ) -> Self {
348 if self.error.is_some() {
349 return self;
350 }
351 let Some(meta) = E::META.field(field) else {
352 self.error = Some(ParamError::UnknownField(format!("#{}", field.0)));
353 return self;
354 };
355 if meta.kind == FieldKind::Aggregate
356 || !operator.accepts_field_type(meta.value_type)
357 || values
358 .iter()
359 .any(|value| value.value_type() != operator.input_type(meta.value_type))
360 {
361 self.error = Some(ParamError::InvalidValue {
362 field: meta.public_name.to_owned(),
363 reason: "scope field type or phase mismatch".to_owned(),
364 });
365 } else if !operator.accepts_arity(values.len()) {
366 self.error = Some(ParamError::WrongArity {
367 field: meta.public_name.to_owned(),
368 operator: operator.short_name(),
369 expected: operator.expected_arity(),
370 actual: values.len(),
371 });
372 } else if ignore_case
373 && (meta.value_type != crate::ValueType::Text || !operator.supports_ignore_case())
374 {
375 self.error = Some(ParamError::IgnoreCaseNotAllowed(
376 meta.public_name.to_owned(),
377 ));
378 } else {
379 if let Some(error) = values.iter().find_map(|value| value.validate().err()) {
380 self.error = Some(error);
381 return self;
382 }
383 self.conditions.push(Condition {
384 field,
385 operator,
386 values,
387 ignore_case,
388 phase: PredicatePhase::Row,
389 });
390 }
391 self
392 }
393
394 pub fn build(self) -> Result<ServerScope<E>, ParamError> {
395 if let Some(error) = self.error {
396 return Err(error);
397 }
398 if self.conditions.is_empty() {
399 return Err(ParamError::InvalidValue {
400 field: "server scope".to_owned(),
401 reason: "empty scope must use ServerScope::unrestricted() explicitly".to_owned(),
402 });
403 }
404 validate_conditions(self.conditions.iter(), self.limits)?;
405 Ok(ServerScope {
406 predicate: PredicateArena::combine_and(
407 self.conditions
408 .into_iter()
409 .map(PredicateArena::from_condition),
410 ),
411 schema_fingerprint: E::META.schema_fingerprint,
412 field_access: crate::FieldAccessPolicy::unrestricted(),
413 _entity: PhantomData,
414 })
415 }
416}