Skip to main content

sift_query_core/policy/
validation.rs

1use super::{QueryPolicy, is_name};
2use crate::{Codec, ParamError};
3use std::any::TypeId;
4use std::sync::Arc;
5
6impl QueryPolicy {
7    /// Iterates the codecs that are actually bound to an entity's result
8    /// fields. The binding list is validated and cached per policy/entity.
9    pub fn for_each_codec_binding<E: crate::SearchEntity>(
10        &self,
11        mut callback: impl FnMut(&'static crate::FieldMeta, &dyn Codec),
12    ) -> Result<(), ParamError> {
13        let key = TypeId::of::<E>();
14        let bindings = {
15            let mut cache = self
16                .codec_bindings
17                .lock()
18                .unwrap_or_else(std::sync::PoisonError::into_inner);
19            cache.get(&key).cloned()
20        };
21        let bindings = if let Some(bindings) = bindings {
22            bindings?
23        } else {
24            let result = self.build_codec_bindings::<E>();
25            self.codec_bindings
26                .lock()
27                .unwrap_or_else(std::sync::PoisonError::into_inner)
28                .insert(key, result.clone());
29            result?
30        };
31        for binding in bindings.iter() {
32            callback(binding.field, binding.codec.as_ref());
33        }
34        Ok(())
35    }
36
37    fn build_codec_bindings<E: crate::SearchEntity>(
38        &self,
39    ) -> Result<Arc<[super::CodecBinding]>, ParamError> {
40        self.validate_codecs::<E>()?;
41        let bindings = E::META
42            .fields
43            .iter()
44            .filter_map(|field| {
45                self.codec_arc_for::<E>(field)
46                    .map(|codec| super::CodecBinding { field, codec })
47            })
48            .collect::<Vec<_>>();
49        Ok(Arc::from(bindings))
50    }
51
52    pub(crate) fn validate_operators<E: crate::SearchEntity>(&self) -> Result<(), ParamError> {
53        let key = TypeId::of::<E>();
54        {
55            let mut cache = self
56                .validated_operators
57                .lock()
58                .unwrap_or_else(std::sync::PoisonError::into_inner);
59            if let Some(result) = cache.get(&key) {
60                return result.clone();
61            }
62        }
63        let result = self.validate_operators_uncached::<E>();
64        self.validated_operators
65            .lock()
66            .unwrap_or_else(std::sync::PoisonError::into_inner)
67            .insert(key, result.clone());
68        result
69    }
70
71    fn validate_operators_uncached<E: crate::SearchEntity>(&self) -> Result<(), ParamError> {
72        for field in E::META.fields {
73            let mut names = std::collections::HashSet::new();
74            for name in E::custom_operator_names(field.id) {
75                let registration =
76                    self.registered_operator(name)
77                        .ok_or_else(|| ParamError::InvalidValue {
78                            field: field.public_name.to_owned(),
79                            reason: "custom operator is not registered".to_owned(),
80                        })?;
81                if !names.insert(*name)
82                    || registration.field_type != field.value_type
83                    || !field.filterable
84                {
85                    return Err(ParamError::InvalidValue {
86                        field: field.public_name.to_owned(),
87                        reason: "duplicate or incompatible custom operator whitelist".to_owned(),
88                    });
89                }
90            }
91        }
92        Ok(())
93    }
94
95    pub(crate) fn validate_entity_names<E: crate::SearchEntity>(&self) -> Result<(), ParamError> {
96        let key = TypeId::of::<E>();
97        {
98            let mut cache = self
99                .validated_names
100                .lock()
101                .unwrap_or_else(std::sync::PoisonError::into_inner);
102            if let Some(result) = cache.get(&key) {
103                return result.clone();
104            }
105        }
106        let result = crate::protocol::parser::validate_entity_names::<E>(self);
107        self.validated_names
108            .lock()
109            .unwrap_or_else(std::sync::PoisonError::into_inner)
110            .insert(key, result.clone());
111        result
112    }
113
114    pub fn codec_for<E: crate::SearchEntity>(
115        &self,
116        field: &crate::FieldMeta,
117    ) -> Option<&dyn Codec> {
118        match E::codec_name(field.id) {
119            Some(name) => self
120                .codecs
121                .iter()
122                .find(|codec| codec.name() == name)
123                .map(Arc::as_ref),
124            None => self.codecs.find(field),
125        }
126    }
127
128    fn codec_arc_for<E: crate::SearchEntity>(
129        &self,
130        field: &crate::FieldMeta,
131    ) -> Option<Arc<dyn Codec>> {
132        match E::codec_name(field.id) {
133            Some(name) => self
134                .codecs
135                .iter()
136                .find(|codec| codec.name() == name)
137                .cloned(),
138            None => self.codecs.find_arc(field),
139        }
140    }
141
142    pub(crate) fn validate_codecs<E: crate::SearchEntity>(&self) -> Result<(), ParamError> {
143        let key = TypeId::of::<E>();
144        {
145            let mut cache = self
146                .validated_codecs
147                .lock()
148                .unwrap_or_else(std::sync::PoisonError::into_inner);
149            if let Some(result) = cache.get(&key) {
150                return result.clone();
151            }
152        }
153        let result = self.validate_codecs_uncached::<E>();
154        self.validated_codecs
155            .lock()
156            .unwrap_or_else(std::sync::PoisonError::into_inner)
157            .insert(key, result.clone());
158        result
159    }
160
161    fn validate_codecs_uncached<E: crate::SearchEntity>(&self) -> Result<(), ParamError> {
162        let mut names = std::collections::HashSet::new();
163        for codec in self.codecs.iter() {
164            if !is_name(codec.name()) || !names.insert(codec.name()) {
165                return Err(ParamError::ConflictingParameters(
166                    "invalid or duplicate codec registration".to_owned(),
167                ));
168            }
169        }
170        for field in E::META.fields {
171            if E::codec_name(field.id).is_some() {
172                if self
173                    .codec_for::<E>(field)
174                    .is_none_or(|codec| !codec.supports(field))
175                {
176                    return Err(ParamError::InvalidValue {
177                        field: field.public_name.to_owned(),
178                        reason: "named codec is missing or incompatible".to_owned(),
179                    });
180                }
181            } else if self
182                .codecs
183                .iter()
184                .filter(|codec| codec.supports(field))
185                .count()
186                > 1
187            {
188                return Err(ParamError::ConflictingParameters(
189                    "ambiguous codec binding".to_owned(),
190                ));
191            }
192        }
193        Ok(())
194    }
195}