1use std::collections::BTreeMap;
2
3use indexmap::IndexMap;
4use indexmap::map::Entry;
5use sift_query_algorithm::BoundedCounter;
6
7mod group_expr;
8mod options;
9
10use crate::policy::names::is_name;
11use crate::{
12 CompatibilityPolicy, Condition, FieldKind, FieldMeta, Operator, Paging, ParamError,
13 PredicateArena, PredicatePhase, QueryPolicy, RawParams, ScalarValue, SearchEntity, UserQuery,
14 ValueType,
15};
16use group_expr::parse_group_expression;
17use options::{parse_order, parse_paging, parse_projection};
18
19pub struct ParamParser<'policy> {
21 policy: &'policy QueryPolicy,
22}
23
24impl<'policy> ParamParser<'policy> {
25 pub fn new(policy: &'policy QueryPolicy) -> Result<Self, ParamError> {
26 Ok(Self { policy })
27 }
28
29 pub fn parse<E: SearchEntity>(&self, raw: &RawParams) -> Result<UserQuery<E>, ParamError> {
30 validate_raw_limits(raw, self.policy)?;
31 self.policy.validate_codecs::<E>()?;
32 self.policy.validate_operators::<E>()?;
33 let raw = self.policy.apply_raw_filters::<E>(raw)?;
34 validate_raw_limits(&raw, self.policy)?;
35 self.policy.validate_entity_names::<E>()?;
36 let mut controls = Controls::default();
37 let mut fields: IndexMap<ConditionKey<'_>, FieldAccumulator<'_>> = IndexMap::new();
38
39 for (key, value) in raw.entries() {
40 if controls.capture(self.policy, key, value)? {
41 continue;
42 }
43 let parsed = parse_field_key(key)?;
44 let Some(meta) = E::META.resolve_field(parsed.field) else {
45 if self.policy.compatibility() == CompatibilityPolicy::BeanSearcher4812 {
46 continue;
47 }
48 return Err(ParamError::UnknownField(parsed.field.to_owned()));
49 };
50 let condition_key = ConditionKey {
51 group: parsed.group,
52 field: parsed.field,
53 };
54 let accumulator = match fields.entry(condition_key) {
55 Entry::Occupied(entry) => entry.into_mut(),
56 Entry::Vacant(entry) => {
57 let key = *entry.key();
58 entry.insert(FieldAccumulator::new(meta, key.group, key.field))
59 }
60 };
61 accumulator.capture(parsed.component, value, self.policy)?;
62 }
63
64 let predicate = self.build_predicate::<E>(fields, controls.group_expr.as_deref())?;
65 let projection = parse_projection::<E>(
66 controls.only_select.as_deref(),
67 controls.select_exclude.as_deref(),
68 )?;
69 let order = parse_order::<E>(
70 self.policy,
71 controls.sort.as_deref(),
72 controls.order.as_deref(),
73 controls.order_by.as_deref(),
74 )?;
75 let paging = parse_paging::<E>(
76 self.policy,
77 controls.page.as_deref(),
78 controls.size.as_deref(),
79 controls.offset.as_deref(),
80 controls.cursor.as_deref(),
81 &order,
82 );
83 validate_order_bind_budget::<E>(&predicate, &order, self.policy.limits())?;
84 let (paging, force_empty) = match paging {
85 Ok(paging) => (paging, false),
86 Err(ParamError::PagingOutOfRange(_))
87 if self.policy.compatibility() == CompatibilityPolicy::BeanSearcher4812 =>
88 {
89 (Paging::default(), true)
90 }
91 Err(error) => return Err(error),
92 };
93
94 Ok(UserQuery::from_parts(
95 predicate,
96 projection,
97 order,
98 paging,
99 E::META.schema_fingerprint,
100 self.policy.fingerprint(),
101 force_empty,
102 ))
103 }
104
105 fn build_predicate<'a, E: SearchEntity>(
106 &self,
107 accumulators: IndexMap<ConditionKey<'a>, FieldAccumulator<'a>>,
108 group_expr: Option<&str>,
109 ) -> Result<PredicateArena, ParamError> {
110 let limits = self.policy.limits();
111 if accumulators.len() > limits.max_conditions {
112 return Err(ParamError::TooComplex("condition count"));
113 }
114
115 let mut logical_values = BoundedCounter::new(limits.max_logical_values);
116 let mut bind_slots = 0usize;
117 let mut root_conditions = Vec::new();
118 let mut grouped_conditions: BTreeMap<String, Vec<Condition>> = BTreeMap::new();
119 for (key, accumulator) in accumulators {
120 let condition =
121 accumulator.finish::<E>(limits.max_values_per_condition, self.policy)?;
122 for value in &condition.values {
123 logical_values
124 .try_add(value.logical_weight())
125 .map_err(|_| ParamError::TooComplex("logical value count"))?;
126 }
127 bind_slots += match condition.operator {
128 Operator::IsNull
129 | Operator::NotNull
130 | Operator::Empty
131 | Operator::NotEmpty
132 | Operator::AlwaysTrue
133 | Operator::AlwaysFalse => 0,
134 Operator::InList | Operator::NotIn => 1,
135 _ => condition.values.len(),
136 };
137 if bind_slots > limits.max_bind_slots {
138 return Err(ParamError::TooComplex("database bind slot count"));
139 }
140 match key.group {
141 Some(group) => grouped_conditions
142 .entry(group.to_owned())
143 .or_default()
144 .push(condition),
145 None => root_conditions.push(condition),
146 }
147 }
148
149 root_conditions.sort_by_key(|condition| condition.field);
150 for conditions in grouped_conditions.values_mut() {
151 conditions.sort_by_key(|condition| condition.field);
152 }
153
154 let root = conditions_to_and(root_conditions);
155 let named = grouped_conditions
156 .into_iter()
157 .map(|(name, conditions)| (name, conditions_to_and(conditions)))
158 .collect::<BTreeMap<_, _>>();
159
160 let grouped = match group_expr {
161 None => PredicateArena::combine_and(named.into_values()),
162 Some(expression) => {
163 if expression.len() > limits.max_group_expr_bytes {
164 return Err(ParamError::TooComplex("gexpr length"));
165 }
166 parse_group_expression(
167 expression,
168 &named,
169 limits.max_group_depth,
170 limits.max_group_nodes,
171 )?
172 }
173 };
174
175 let predicate = PredicateArena::combine_and([root, grouped]);
176 predicate.partition()?;
177 Ok(predicate)
178 }
179}
180
181pub(crate) fn validate_order_bind_budget<E: SearchEntity>(
182 predicate: &PredicateArena,
183 order: &[crate::OrderBy],
184 limits: &crate::QueryLimits,
185) -> Result<(), ParamError> {
186 let extra = order
187 .iter()
188 .filter(|order| E::condition_order(order.field).is_some())
189 .count();
190 if extra == 0 {
191 return Ok(());
192 }
193 let binds = predicate
194 .nodes()
195 .iter()
196 .filter_map(|node| match node {
197 crate::PredicateNode::Condition(condition) => Some(
198 if matches!(condition.operator, Operator::InList | Operator::NotIn) {
199 1
200 } else {
201 condition.values.len()
202 },
203 ),
204 _ => None,
205 })
206 .sum::<usize>();
207 if binds.saturating_add(extra) > limits.max_bind_slots {
208 return Err(ParamError::TooComplex("database bind slot count"));
209 }
210 Ok(())
211}
212
213fn validate_raw_limits(raw: &RawParams, policy: &QueryPolicy) -> Result<(), ParamError> {
214 let limits = policy.limits();
215 if raw.raw_bytes() > limits.max_raw_query_bytes {
216 return Err(ParamError::QueryTooLong);
217 }
218 if raw.len() > limits.max_raw_items {
219 return Err(ParamError::TooComplex("raw parameter item count"));
220 }
221 if raw.decoded_bytes() > limits.max_decoded_bytes {
222 return Err(ParamError::TooComplex("decoded parameter bytes"));
223 }
224 if raw.entries().iter().any(|(key, value)| {
225 key.len() > limits.max_key_bytes || value.len() > limits.max_value_bytes
226 }) {
227 return Err(ParamError::TooComplex("parameter key or value length"));
228 }
229 Ok(())
230}
231
232pub(crate) fn validate_entity_names<E: SearchEntity>(
233 policy: &QueryPolicy,
234) -> Result<(), ParamError> {
235 for field in E::META.fields {
236 if !is_name(field.public_name) || field.public_name.len() > 64 {
237 return Err(ParamError::InvalidName(field.public_name.to_owned()));
238 }
239 if policy.names().all().contains(&field.public_name) {
240 return Err(ParamError::ConflictingParameters(format!(
241 "field {} conflicts with a control parameter",
242 field.public_name
243 )));
244 }
245 }
246 Ok(())
247}
248
249#[derive(Debug, Clone, Default)]
250struct Controls {
251 page: Option<String>,
252 size: Option<String>,
253 offset: Option<String>,
254 cursor: Option<String>,
255 sort: Option<String>,
256 order: Option<String>,
257 order_by: Option<String>,
258 group_expr: Option<String>,
259 only_select: Option<String>,
260 select_exclude: Option<String>,
261}
262
263impl Controls {
264 fn capture(
265 &mut self,
266 policy: &QueryPolicy,
267 key: &str,
268 value: &str,
269 ) -> Result<bool, ParamError> {
270 let names = policy.names();
271 let target = if key == names.page {
272 Some(&mut self.page)
273 } else if key == names.size {
274 Some(&mut self.size)
275 } else if key == names.offset {
276 Some(&mut self.offset)
277 } else if key == names.cursor {
278 Some(&mut self.cursor)
279 } else if key == names.sort {
280 Some(&mut self.sort)
281 } else if key == names.order {
282 Some(&mut self.order)
283 } else if key == names.order_by {
284 Some(&mut self.order_by)
285 } else if key == names.group_expr {
286 Some(&mut self.group_expr)
287 } else if key == names.only_select {
288 Some(&mut self.only_select)
289 } else if key == names.select_exclude {
290 Some(&mut self.select_exclude)
291 } else {
292 None
293 };
294 let Some(target) = target else {
295 return Ok(false);
296 };
297 if target.is_some() {
298 return Err(ParamError::DuplicateParameter(key.to_owned()));
299 }
300 *target = Some(value.to_owned());
301 Ok(true)
302 }
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
306struct ConditionKey<'a> {
307 group: Option<&'a str>,
308 field: &'a str,
309}
310
311#[derive(Clone, Copy)]
312enum FieldComponent {
313 Value,
314 Indexed(usize),
315 Operator,
316 IgnoreCase,
317}
318
319struct ParsedFieldKey<'a> {
320 group: Option<&'a str>,
321 field: &'a str,
322 component: FieldComponent,
323}
324
325fn parse_field_key(key: &str) -> Result<ParsedFieldKey<'_>, ParamError> {
326 let (base, component) = if let Some(base) = key.strip_suffix("-op") {
327 (base, FieldComponent::Operator)
328 } else if let Some(base) = key.strip_suffix("-ic") {
329 (base, FieldComponent::IgnoreCase)
330 } else if let Some((base, suffix)) = key.rsplit_once('-') {
331 if suffix.bytes().all(|byte| byte.is_ascii_digit()) {
332 let index = suffix
333 .parse()
334 .map_err(|_| ParamError::InvalidName(key.to_owned()))?;
335 if suffix.len() > 1 && suffix.starts_with('0') {
336 return Err(ParamError::InvalidName(key.to_owned()));
337 }
338 (base, FieldComponent::Indexed(index))
339 } else {
340 return Err(ParamError::UnknownParameter(key.to_owned()));
341 }
342 } else {
343 (key, FieldComponent::Value)
344 };
345
346 let (group, field) = if let Some((group, field)) = base.split_once('.') {
347 if field.contains('.') {
348 return Err(ParamError::InvalidName(key.to_owned()));
349 }
350 (Some(group), field)
351 } else {
352 (None, base)
353 };
354 if field.len() > 64
355 || !is_name(field)
356 || group
357 .as_ref()
358 .is_some_and(|name| name.len() > 64 || !is_name(name))
359 {
360 return Err(ParamError::InvalidName(key.to_owned()));
361 }
362 Ok(ParsedFieldKey {
363 group,
364 field,
365 component,
366 })
367}
368
369struct FieldAccumulator<'a> {
370 meta: &'static FieldMeta,
371 group: Option<&'a str>,
372 field: &'a str,
373 repeated: Vec<String>,
374 indexed: BTreeMap<usize, String>,
375 operator: Option<Operator>,
376 ignore_case: Option<bool>,
377}
378
379impl<'a> FieldAccumulator<'a> {
380 fn new(meta: &'static FieldMeta, group: Option<&'a str>, field: &'a str) -> Self {
381 Self {
382 meta,
383 group,
384 field,
385 repeated: Vec::new(),
386 indexed: BTreeMap::new(),
387 operator: None,
388 ignore_case: None,
389 }
390 }
391
392 fn display(&self) -> String {
393 format_display(self.group, self.field)
394 }
395
396 fn capture(
397 &mut self,
398 component: FieldComponent,
399 value: &str,
400 policy: &QueryPolicy,
401 ) -> Result<(), ParamError> {
402 match component {
403 FieldComponent::Value => {
404 if !self.indexed.is_empty() {
405 return Err(ParamError::ConflictingParameters(self.display()));
406 }
407 self.repeated.push(value.to_owned());
408 }
409 FieldComponent::Indexed(index) => {
410 if !self.repeated.is_empty() {
411 return Err(ParamError::ConflictingParameters(self.display()));
412 }
413 if self.indexed.insert(index, value.to_owned()).is_some() {
414 return Err(ParamError::DuplicateParameter(format!(
415 "{}-{index}",
416 self.display()
417 )));
418 }
419 }
420 FieldComponent::Operator => {
421 let operator = Operator::parse(value)
422 .or_else(|| {
423 policy
424 .registered_operator(value)
425 .map(|registration| Operator::Custom(registration.operator))
426 })
427 .ok_or_else(|| ParamError::InvalidValue {
428 field: self.display(),
429 reason: format!("unknown operator {value}"),
430 })?;
431 if self.operator.replace(operator).is_some() {
432 return Err(ParamError::DuplicateParameter(format!(
433 "{}-op",
434 self.display()
435 )));
436 }
437 }
438 FieldComponent::IgnoreCase => {
439 let ignore_case = match value {
440 "true" => true,
441 "false" => false,
442 _ => {
443 return Err(ParamError::InvalidValue {
444 field: self.display(),
445 reason: "ignore-case must be true or false".to_owned(),
446 });
447 }
448 };
449 if self.ignore_case.replace(ignore_case).is_some() {
450 return Err(ParamError::DuplicateParameter(format!(
451 "{}-ic",
452 self.display()
453 )));
454 }
455 }
456 }
457 Ok(())
458 }
459
460 fn finish<E: SearchEntity>(
461 self,
462 max_values: usize,
463 policy: &QueryPolicy,
464 ) -> Result<Condition, ParamError> {
465 let group = self.group;
466 let field = self.field;
467 if !self.meta.filterable {
468 return Err(ParamError::NotFilterable(format_display(group, field)));
469 }
470 let operator = self.operator.unwrap_or(Operator::Eq);
471 let allowed = match operator {
472 Operator::Custom(custom) => {
473 E::custom_operator_names(self.meta.id).contains(&custom.name())
474 && policy
475 .registered_operator(custom.name())
476 .is_some_and(|item| {
477 item.operator == custom && item.field_type == self.meta.value_type
478 })
479 }
480 builtin => self.meta.operators.contains(builtin),
481 };
482 if !allowed {
483 return Err(ParamError::OperatorNotAllowed {
484 field: format_display(group, field),
485 operator: operator.short_name(),
486 });
487 }
488 let raw_values = if self.indexed.is_empty() {
489 self.repeated
490 } else {
491 let mut values = Vec::with_capacity(self.indexed.len());
492 for (expected, (actual, value)) in self.indexed.into_iter().enumerate() {
493 if actual != expected {
494 return Err(ParamError::NonContiguousIndex {
495 field: format_display(group, field),
496 expected,
497 });
498 }
499 values.push(value);
500 }
501 values
502 };
503 if raw_values.len() > max_values {
504 return Err(ParamError::TooComplex("values per condition"));
505 }
506 if !operator.accepts_arity(raw_values.len()) {
507 return Err(ParamError::WrongArity {
508 field: format_display(group, field),
509 operator: operator.short_name(),
510 expected: operator.expected_arity(),
511 actual: raw_values.len(),
512 });
513 }
514 let ignore_case = self.ignore_case.unwrap_or(false);
515 if ignore_case
516 && (self.meta.value_type != ValueType::Text || !operator.supports_ignore_case())
517 {
518 return Err(ParamError::IgnoreCaseNotAllowed(format_display(
519 group, field,
520 )));
521 }
522 let input_type = operator.input_type(self.meta.value_type);
523 let input_field = FieldMeta {
524 value_type: input_type,
525 ..*self.meta
526 };
527 let values = raw_values
528 .iter()
529 .map(|value| {
530 if let Some(codec) = policy.codec_for::<E>(self.meta) {
531 let parsed = codec.parse(&input_field, value).map_err(|reason| {
532 ParamError::InvalidValue {
533 field: format_display(group, field),
534 reason,
535 }
536 })?;
537 if parsed.value_type() != input_type {
538 return Err(ParamError::InvalidValue {
539 field: format_display(group, field),
540 reason: "codec returned a value with the wrong field type".to_owned(),
541 });
542 }
543 if matches!(&parsed, ScalarValue::F32(value) if !value.is_finite())
544 || matches!(&parsed, ScalarValue::F64(value) if !value.is_finite())
545 {
546 return Err(ParamError::InvalidValue {
547 field: format_display(group, field),
548 reason: "codec returned a non-finite value".to_owned(),
549 });
550 }
551 Ok(parsed)
552 } else {
553 ScalarValue::parse(input_type, value, self.field).map_err(|error| match error {
554 ParamError::InvalidValue { reason, .. } => ParamError::InvalidValue {
555 field: format_display(group, field),
556 reason,
557 },
558 other => other,
559 })
560 }
561 })
562 .collect::<Result<Vec<_>, _>>()?;
563 if values
564 .iter()
565 .map(ScalarValue::logical_weight)
566 .sum::<usize>()
567 > max_values
568 {
569 return Err(ParamError::TooComplex("values per condition"));
570 }
571 for value in &values {
572 value.validate()?;
573 }
574 Ok(Condition {
575 field: self.meta.id,
576 operator,
577 values,
578 ignore_case,
579 phase: if self.meta.kind == FieldKind::Aggregate {
580 PredicatePhase::Group
581 } else {
582 PredicatePhase::Row
583 },
584 })
585 }
586}
587
588fn format_display(group: Option<&str>, field: &str) -> String {
589 group.map_or_else(|| field.to_owned(), |group| format!("{group}.{field}"))
590}
591
592fn conditions_to_and(conditions: Vec<Condition>) -> PredicateArena {
593 PredicateArena::combine_and(conditions.into_iter().map(PredicateArena::from_condition))
594}