sift_query_core/query/scope/
mod.rs1use crate::policy::budget::logical_values;
3use crate::{
4 Condition, FieldId, FieldRef, ParamError, PredicateArena, PredicateNode, SearchEntity,
5};
6mod group;
7mod policy;
8mod row;
9pub use group::GroupScopeBuilder;
10pub use policy::ScopeProvider;
11pub use row::ServerScopeBuilder;
12
13use std::marker::PhantomData;
14
15pub struct FieldAccessPolicy<E: SearchEntity> {
17 schema_fingerprint: u64,
18 allowed: Vec<bool>,
19 _entity: PhantomData<fn() -> E>,
20}
21
22impl<E: SearchEntity> Clone for FieldAccessPolicy<E> {
23 fn clone(&self) -> Self {
24 Self {
25 schema_fingerprint: self.schema_fingerprint,
26 allowed: self.allowed.clone(),
27 _entity: PhantomData,
28 }
29 }
30}
31
32impl<E: SearchEntity> std::fmt::Debug for FieldAccessPolicy<E> {
33 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 formatter
35 .debug_struct("FieldAccessPolicy")
36 .field("schema_fingerprint", &self.schema_fingerprint)
37 .field("allowed", &self.allowed)
38 .finish()
39 }
40}
41
42impl<E: SearchEntity> PartialEq for FieldAccessPolicy<E> {
43 fn eq(&self, other: &Self) -> bool {
44 self.schema_fingerprint == other.schema_fingerprint && self.allowed == other.allowed
45 }
46}
47
48impl<E: SearchEntity> Eq for FieldAccessPolicy<E> {}
49
50impl<E: SearchEntity> FieldAccessPolicy<E> {
51 #[must_use]
52 pub fn unrestricted() -> Self {
53 Self {
54 schema_fingerprint: E::META.schema_fingerprint,
55 allowed: vec![true; E::META.fields.len()],
56 _entity: PhantomData,
57 }
58 }
59
60 #[must_use]
61 pub fn none() -> Self {
62 Self {
63 schema_fingerprint: E::META.schema_fingerprint,
64 allowed: vec![false; E::META.fields.len()],
65 _entity: PhantomData,
66 }
67 }
68
69 #[must_use]
70 pub fn builder() -> FieldAccessPolicyBuilder<E> {
71 FieldAccessPolicyBuilder {
72 policy: Self::none(),
73 error: None,
74 }
75 }
76
77 #[must_use]
78 pub fn allows(&self, field: FieldId) -> bool {
79 self.allowed
80 .get(usize::from(field.0))
81 .copied()
82 .unwrap_or(false)
83 }
84
85 #[must_use]
86 pub fn schema_fingerprint(&self) -> u64 {
87 self.schema_fingerprint
88 }
89
90 pub(crate) fn intersect(&self, other: &Self) -> Self {
91 let allowed = self
92 .allowed
93 .iter()
94 .zip(&other.allowed)
95 .map(|(left, right)| *left && *right)
96 .collect();
97 Self {
98 schema_fingerprint: self.schema_fingerprint,
99 allowed,
100 _entity: PhantomData,
101 }
102 }
103}
104
105#[derive(Debug)]
107pub struct FieldAccessPolicyBuilder<E: SearchEntity> {
108 policy: FieldAccessPolicy<E>,
109 error: Option<ParamError>,
110}
111
112impl<E: SearchEntity> FieldAccessPolicyBuilder<E> {
113 #[must_use]
114 pub fn allow<T, K>(self, field: FieldRef<E, T, K>) -> Self {
115 self.allow_id(field.id())
116 }
117
118 #[must_use]
119 pub fn allow_id(mut self, field: FieldId) -> Self {
120 if self.error.is_some() {
121 return self;
122 }
123 let Some(slot) = self.policy.allowed.get_mut(usize::from(field.0)) else {
124 self.error = Some(ParamError::UnknownField(format!("#{}", field.0)));
125 return self;
126 };
127 *slot = true;
128 self
129 }
130
131 #[must_use]
132 pub fn allow_all(mut self) -> Self {
133 if self.error.is_none() {
134 self.policy.allowed.fill(true);
135 }
136 self
137 }
138
139 pub fn build(self) -> Result<FieldAccessPolicy<E>, ParamError> {
140 self.error.map_or(Ok(self.policy), Err)
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct ScopeLimits {
146 pub max_conditions: usize,
147 pub max_logical_values: usize,
148}
149
150impl Default for ScopeLimits {
151 fn default() -> Self {
152 Self {
153 max_conditions: 64,
154 max_logical_values: 512,
155 }
156 }
157}
158
159fn validate_predicates<'a>(
160 predicates: impl Iterator<Item = &'a PredicateArena> + Clone,
161 limits: ScopeLimits,
162) -> Result<(), ParamError> {
163 let conditions = predicates
164 .flat_map(PredicateArena::nodes)
165 .filter_map(|node| {
166 if let PredicateNode::Condition(condition) = node {
167 Some(condition)
168 } else {
169 None
170 }
171 });
172 validate_conditions(conditions, limits)
173}
174
175fn validate_conditions<'a>(
176 conditions: impl Iterator<Item = &'a Condition> + Clone,
177 limits: ScopeLimits,
178) -> Result<(), ParamError> {
179 let condition_count = conditions.clone().count();
180 if condition_count > limits.max_conditions {
181 return Err(ParamError::TooComplex("server scope condition count"));
182 }
183 logical_values(
184 conditions.flat_map(|condition| &condition.values),
185 limits.max_logical_values,
186 "server scope logical value count",
187 )?;
188 Ok(())
189}