graphql_starter/
queried_fields.rs

1//! Utilities to include queried fields in APIs
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5/// This type represents the fields queried by a request.
6///
7/// Nested fields are allowed using a dot (`.`) separator.
8/// ```ignore
9/// vec![
10///   "fieldOne",
11///   "fieldTwo.child",
12///   "fieldTwo.otherChild.field"
13/// ];
14/// ```
15///
16/// It's serialized as an optional `Vec<String>` so it can be directly used in query strings or bodies.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum QueriedFields {
19    /// Query all the fields available
20    All,
21    /// query just the listed fields
22    Fields(Vec<String>),
23}
24impl Serialize for QueriedFields {
25    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
26    where
27        S: Serializer,
28    {
29        match self {
30            QueriedFields::All => serializer.serialize_none(),
31            QueriedFields::Fields(fields) => serializer.serialize_some(fields),
32        }
33    }
34}
35impl<'de> Deserialize<'de> for QueriedFields {
36    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37    where
38        D: Deserializer<'de>,
39    {
40        let fields: Option<Vec<String>> = serde::Deserialize::deserialize(deserializer)?;
41        match fields {
42            Some(fields) => Ok(Self::Fields(fields)),
43            None => Ok(Self::All),
44        }
45    }
46}
47impl From<Option<Vec<String>>> for QueriedFields {
48    fn from(value: Option<Vec<String>>) -> Self {
49        match value {
50            Some(fields) => QueriedFields::Fields(fields),
51            None => QueriedFields::All,
52        }
53    }
54}
55impl From<QueriedFields> for Option<Vec<String>> {
56    fn from(value: QueriedFields) -> Self {
57        match value {
58            QueriedFields::All => None,
59            QueriedFields::Fields(fields) => Some(fields),
60        }
61    }
62}
63impl From<Vec<String>> for QueriedFields {
64    fn from(value: Vec<String>) -> Self {
65        Self::Fields(value)
66    }
67}
68impl From<&[String]> for QueriedFields {
69    fn from(value: &[String]) -> Self {
70        Self::Fields(value.to_vec())
71    }
72}
73impl From<Vec<&str>> for QueriedFields {
74    fn from(value: Vec<&str>) -> Self {
75        Self::Fields(value.into_iter().map(String::from).collect())
76    }
77}
78impl From<&[&str]> for QueriedFields {
79    fn from(value: &[&str]) -> Self {
80        Self::Fields(value.iter().map(|&s| s.into()).collect())
81    }
82}
83
84impl QueriedFields {
85    /// Returns wether every field is being queried or not
86    pub fn all_fields_queried(&self) -> bool {
87        match self {
88            QueriedFields::All => true,
89            QueriedFields::Fields(_) => false,
90        }
91    }
92
93    /// Checks wether no fields are being queried
94    pub fn is_empty(&self) -> bool {
95        match self {
96            QueriedFields::All => false,
97            QueriedFields::Fields(f) => f.is_empty(),
98        }
99    }
100
101    /// Checks if a given field is being queried.
102    ///
103    /// ## Examples:
104    /// ``` rust
105    /// # use graphql_starter::queried_fields::QueriedFields;
106    /// let query = QueriedFields::from(vec!["a", "a.b.c", "a.b.d", "b", "c.d"]);
107    ///
108    /// assert!(query.contains("a"));
109    /// assert!(query.contains("a.b"));
110    /// assert!(!query.contains("d"));
111    /// ```
112    pub fn contains(&self, field: &str) -> bool {
113        match self {
114            QueriedFields::All => true,
115            QueriedFields::Fields(fields) => {
116                let prefix = format!("{field}.");
117                fields.iter().any(|f| f == field || f.starts_with(&prefix))
118            }
119        }
120    }
121
122    /// Checks if there are other top-level fields queried not present on the list.
123    ///
124    /// This method will return true always if all fields are queried, no matter if you manually provide all of them.
125    ///
126    /// # Examples:
127    /// ``` rust
128    /// # use graphql_starter::queried_fields::QueriedFields;
129    /// let query = QueriedFields::from(vec!["a", "b", "b.d", "c"]);
130    ///
131    /// assert!(query.other_than(&["a", "b"]));
132    /// assert!(!query.other_than(&["a", "b", "c"]));
133    /// ```
134    pub fn other_than(&self, fields: &[&str]) -> bool {
135        match self {
136            QueriedFields::All => true,
137            QueriedFields::Fields(queried_fields) => queried_fields.iter().any(|f| match f.split_once('.') {
138                Some((f, _)) => !fields.contains(&f),
139                None => !fields.contains(&f.as_str()),
140            }),
141        }
142    }
143
144    /// Returns the [QueriedFields] for a given child field.
145    ///
146    /// ## Examples:
147    /// ``` rust
148    /// # use graphql_starter::queried_fields::QueriedFields;
149    /// let query = QueriedFields::from(vec!["a", "a.b.c", "a.b.d", "b", "c.d"]);
150    ///
151    /// assert!(query.child("d").is_empty());
152    /// assert!(query.child("a.b").contains("c"));
153    /// assert!(query.child("a.b").contains("d"));
154    /// ```
155    pub fn child(&self, field: &str) -> QueriedFields {
156        match self {
157            QueriedFields::All => QueriedFields::All,
158            QueriedFields::Fields(fields) => {
159                if fields.is_empty() {
160                    QueriedFields::Fields(Vec::default())
161                } else {
162                    let prefix = format!("{field}.");
163                    QueriedFields::Fields(
164                        fields
165                            .iter()
166                            .filter_map(|s| s.strip_prefix(&prefix).map(|stripped| stripped.to_string()))
167                            .collect(),
168                    )
169                }
170            }
171        }
172    }
173
174    #[cfg(feature = "graphql")]
175    /// Returns the [QueriedFields] for the [Edge's](crate::pagination::Edge) `node` in a
176    /// [Page](crate::pagination::Page).
177    ///
178    /// ## Examples:
179    /// ``` rust
180    /// # use graphql_starter::queried_fields::QueriedFields;
181    /// let query = QueriedFields::from(vec![
182    ///     "pageInfo.hasNextCursor",
183    ///     "pageInfo.endCursor",
184    ///     "totalItems",
185    ///     "edges.node.a",
186    ///     "nodes.b",
187    /// ]);
188    ///
189    /// assert!(query.nodes().contains("a"));
190    /// assert!(query.nodes().contains("b"));
191    /// assert!(!query.nodes().contains("c"));
192    /// ```
193    pub fn nodes(&self) -> QueriedFields {
194        match self {
195            QueriedFields::All => QueriedFields::All,
196            QueriedFields::Fields(fields) => {
197                if fields.is_empty() {
198                    QueriedFields::Fields(Vec::default())
199                } else {
200                    match (self.child("nodes"), self.child("edges").child("node")) {
201                        (QueriedFields::Fields(mut nodes_fields), QueriedFields::Fields(mut edges_fields)) => {
202                            nodes_fields.append(&mut edges_fields);
203                            nodes_fields.sort();
204                            nodes_fields.dedup();
205                            QueriedFields::Fields(nodes_fields)
206                        }
207                        _ => QueriedFields::All,
208                    }
209                }
210            }
211        }
212    }
213
214    #[cfg(feature = "graphql")]
215    /// Returns the [QueriedFields] for the [GraphQLMap's](crate::graphql::GraphQLMap)
216    /// [entry](crate::graphql::GraphQLMapEntry) `values`.
217    ///
218    /// It's really a shortcut to `.child("value")`
219    ///
220    /// ## Examples:
221    /// ``` rust
222    /// # use graphql_starter::queried_fields::QueriedFields;
223    /// let query = QueriedFields::from(vec!["a", "b.key", "b.value.c", "b.value.d"]);
224    ///
225    /// assert!(query.child("b").entry_values().contains("c"));
226    /// assert!(query.child("b").entry_values().contains("d"));
227    /// ```
228    pub fn entry_values(&self) -> QueriedFields {
229        self.child("value")
230    }
231}