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}