Skip to main content

ciboulette/query/
parsing.rs

1use super::*;
2use serde::de::{DeserializeSeed, Deserializer};
3/// ## Element of a sorting list.
4#[derive(Debug, Getters, Clone, Ord, PartialEq, Eq, PartialOrd)]
5#[getset(get = "pub")]
6pub struct CibouletteSortingElement {
7    /// The relation chain
8    pub rel_chain: Vec<CibouletteResourceRelationshipDetails>,
9    /// The direction of the sort
10    pub direction: CibouletteSortingDirection,
11    /// The field that is beeing sorted
12    pub field: ArcStr,
13}
14
15impl CibouletteSortingElement {
16    /// Create a new sorting element
17    pub fn new(
18        rel_chain: Vec<CibouletteResourceRelationshipDetails>,
19        direction: CibouletteSortingDirection,
20        field: ArcStr,
21    ) -> Self {
22        CibouletteSortingElement {
23            rel_chain,
24            direction,
25            field,
26        }
27    }
28}
29
30/// ## Builder object for [CibouletteQueryParameters](CibouletteQueryParameters)
31#[derive(Debug, Getters)]
32#[getset(get = "pub")]
33pub struct CibouletteQueryParametersBuilder<'request> {
34    /// Include other resource with response
35    pub(super) include: Option<Vec<Vec<Cow<'request, str>>>>,
36    /// Don't include all the field for some types
37    pub(super) sparse: BTreeMap<Cow<'request, str>, Vec<Cow<'request, str>>>,
38    /// Directive to sort the main type
39    pub(super) sorting: Vec<(CibouletteSortingDirection, Cow<'request, str>)>,
40    /// Paginate the response
41    pub(super) page: BTreeMap<CiboulettePageType<'request>, Cow<'request, str>>,
42    /// TBD
43    pub(super) filter: Option<Cow<'request, str>>,
44    /// TBD
45    pub(super) filter_typed: BTreeMap<Cow<'request, str>, Cow<'request, str>>,
46    /// The other query parameters
47    pub(super) meta: BTreeMap<Cow<'request, str>, Cow<'request, str>>,
48}
49
50/// ## Query parameters for `json:api`
51#[derive(Debug, Getters, Default, Clone)]
52#[getset(get = "pub")]
53pub struct CibouletteQueryParameters<'request> {
54    pub include: Vec<Vec<CibouletteResourceRelationshipDetails>>,
55    pub sparse: BTreeMap<Arc<CibouletteResourceType>, Vec<ArcStr>>,
56    pub sorting: Vec<CibouletteSortingElement>,
57    pub page: BTreeMap<CiboulettePageType<'request>, Cow<'request, str>>,
58    pub filter: Option<Cow<'request, str>>,
59    pub filter_typed: BTreeMap<Cow<'request, str>, Cow<'request, str>>,
60    pub meta: BTreeMap<Cow<'request, str>, Cow<'request, str>>,
61}
62
63impl<'de> Deserialize<'de> for CibouletteQueryParametersBuilder<'de> {
64    #[inline]
65    fn deserialize<D>(deserializer: D) -> Result<CibouletteQueryParametersBuilder<'de>, D::Error>
66    where
67        D: Deserializer<'de>,
68    {
69        let visitor = CibouletteQueryParametersBuilderVisitor;
70
71        visitor.deserialize(deserializer)
72    }
73}
74
75impl<'request> CibouletteQueryParametersBuilder<'request> {
76    // }
77    /// Check that a relationship exists between a chain of types.
78    ///
79    /// i.e. "author.comments" makes sense because the author has comments
80    ///
81    /// but "comments.email" may not make sense
82    /// if there is no relationship between those two resources.
83    #[inline]
84    pub(super) fn check_relationship_exists(
85        store: &CibouletteStore,
86        main_type: &Arc<CibouletteResourceType>,
87        rel_list: &[Cow<'request, str>],
88    ) -> Result<Vec<CibouletteResourceRelationshipDetails>, CibouletteError> {
89        let mut current_type = main_type.clone();
90        let mut res: Vec<CibouletteResourceRelationshipDetails> = Vec::new();
91        let mut first = true;
92        for rel in rel_list {
93            if first {
94                first = false;
95                if current_type.name().as_str() == rel.as_ref() {
96                    continue;
97                }
98            }
99            let tmp = current_type.get_relationship_details(store, rel)?.clone();
100            current_type = tmp.related_type().clone();
101            res.push(tmp);
102        }
103        if res.is_empty() {
104            return Err(CibouletteError::UnknownRelationship(
105                main_type.name().to_string(),
106                "<empty>".to_string(),
107            ));
108        }
109        Ok(res)
110    }
111
112    /// Checks that a field exists in a give resource type
113    #[inline]
114    pub(super) fn check_field_exists(
115        type_: &Arc<CibouletteResourceType>,
116        field: &str,
117    ) -> Result<ArcStr, CibouletteError> {
118        match type_.schema().properties().get_key_value(field) {
119            Some((k, _)) => Ok(k.clone()),
120            None => Err(CibouletteError::UnknownField(
121                type_.name().to_string(),
122                field.to_string(),
123            )),
124        }
125    }
126
127    /// Checks that fields exists in a give resource type
128    #[inline]
129    pub(super) fn check_fields_exists(
130        type_: &CibouletteResourceType,
131        field_list: Vec<Cow<'request, str>>,
132    ) -> Result<Vec<ArcStr>, CibouletteError> {
133        let curr_obj: &MessyJsonObject = type_.schema();
134        let mut res: Vec<ArcStr> = Vec::with_capacity(field_list.len());
135        let mut iter = field_list.iter().peekable();
136
137        while let Some(field) = iter.next() {
138            let (k, _) = curr_obj
139                .properties()
140                .get_key_value(field.as_ref())
141                .ok_or_else(|| {
142                    CibouletteError::UnknownField(type_.name().to_string(), field.to_string())
143                })?;
144            res.push(k.clone());
145            match iter.peek().is_some() {
146                true => continue,
147                false => return Ok(res),
148            }
149        }
150        match field_list.len() {
151            0 => Err(CibouletteError::UnknownField(
152                type_.name().to_string(),
153                "<empty>".to_string(),
154            )),
155            _ => Err(CibouletteError::UnknownField(
156                type_.name().to_string(),
157                field_list.join("."),
158            )),
159        }
160    }
161
162    /// Build a [CibouletteQueryParametersBuilder](CibouletteQueryParametersBuilder) from the builder
163    pub fn build(
164        self,
165        bag: &CibouletteStore,
166        main_type: Arc<CibouletteResourceType>,
167    ) -> Result<CibouletteQueryParameters<'request>, CibouletteError> {
168        let mut sparse: BTreeMap<Arc<CibouletteResourceType>, Vec<ArcStr>> = BTreeMap::new();
169        let mut sorting: Vec<CibouletteSortingElement> = Vec::with_capacity(self.sorting.len());
170
171        // Check for include relationships and build the array
172        let include: Vec<Vec<CibouletteResourceRelationshipDetails>> = match self.include {
173            None => Vec::default(),
174            Some(include) => {
175                let mut res: Vec<Vec<CibouletteResourceRelationshipDetails>> = Vec::new();
176                for types in include.into_iter() {
177                    res.push(Self::check_relationship_exists(
178                        bag,
179                        &main_type,
180                        types.as_slice(),
181                    )?);
182                }
183                res
184            }
185        };
186
187        // Check for sparse fields, checking that fields exists
188        for (type_, fields) in self.sparse.into_iter() {
189            let rel = bag.get_type(type_.as_ref())?;
190            let fields = match fields.is_empty() {
191                true => vec![],
192                false => Self::check_fields_exists(&rel, fields)?,
193            };
194            sparse.insert(rel.clone(), fields);
195        }
196
197        // Check for the sort fields, checking fields exists
198        if !self.sorting.is_empty() {
199            for (direction, field) in self.sorting.into_iter() {
200                sorting.push(sorting::extract_type(
201                    &bag,
202                    main_type.clone(),
203                    direction,
204                    field,
205                )?)
206            }
207        }
208        let res = CibouletteQueryParameters {
209            include,
210            page: self.page,
211            meta: self.meta,
212            filter: self.filter,
213            filter_typed: self.filter_typed,
214            sparse,
215            sorting,
216        };
217        Ok(res)
218    }
219}