graphql_query/json/
values.rs

1use bumpalo::collections::Vec;
2use hashbrown::HashMap;
3use serde_json::{json, map::Map as JSMap, Value as JSValue};
4
5use super::ValueFromNode;
6use crate::ast::*;
7use crate::error::{Error, Result};
8
9/// Convert [serde_json::Value] to [Variables] given [VariableDefinitions].
10///
11/// This may be used to accept JSON values as variables for a given operation definition, which
12/// contains the necessary types to cast JSON values to variables.
13pub fn ast_variables_from_value<'a>(
14    ctx: &'a ASTContext,
15    input: &JSValue,
16    var_defs: &'a VariableDefinitions<'a>,
17) -> Result<Variables<'a>> {
18    let mut vars = HashMap::new_in(&ctx.arena);
19    if var_defs.is_empty() {
20        Ok(vars)
21    } else if let JSValue::Object(obj) = input {
22        for var_def in var_defs.children.iter() {
23            let value = match obj.get(var_def.variable.name) {
24                Some(value) => ctx.alloc(ast_from_value(ctx, value, ctx.alloc(var_def.of_type))?),
25                None => match (var_def.default_value.clone(), var_def.of_type) {
26                    (Value::List(_), Type::ListType(_)) => &var_def.default_value,
27                    (Value::Null, Type::ListType(_)) => &var_def.default_value,
28                    (default_value, Type::ListType(_)) => {
29                        let mut builder: Vec<'_, _> = Vec::new_in(&ctx.arena);
30                        builder.push(default_value);
31                        ctx.alloc(Value::List(ListValue { children: builder }))
32                    }
33                    _ => &var_def.default_value,
34                },
35            };
36            vars.insert(var_def.variable.name, value.to_owned());
37        }
38        Ok(vars)
39    } else {
40        Err(Error::new(
41            "Variables expected but received non-object value",
42            None,
43        ))
44    }
45}
46
47/// Convert [serde_json::Value] to an AST Value Node given a [Type] definition.
48pub fn ast_from_value<'a>(
49    ctx: &'a ASTContext,
50    value: &JSValue,
51    of_type: &'a Type<'a>,
52) -> Result<Value<'a>> {
53    match (of_type, value) {
54        (Type::ListType(of_type), JSValue::Array(list)) => {
55            let new_list = list.iter().map(|value| ast_from_value(ctx, value, of_type));
56
57            let mut new_list_children = Vec::new_in(&ctx.arena);
58            for item in new_list {
59                new_list_children.push(item?);
60            }
61
62            return Ok(Value::List(ListValue {
63                children: new_list_children,
64            }));
65        }
66        (Type::ListType(of_type), value) => {
67            if matches!(value, JSValue::Null) {
68                return Ok(Value::Null);
69            }
70
71            let child = ast_from_value(ctx, value, of_type)?;
72            let mut new_list_children = Vec::new_in(&ctx.arena);
73            new_list_children.push(child);
74            return Ok(Value::List(ListValue {
75                children: new_list_children,
76            }));
77        }
78
79        (Type::NonNullType(_), JSValue::Null) => {
80            Err(Error::new("Received null for non-nullable type", None))
81        }
82
83        (_, JSValue::Null) => Ok(Value::Null),
84
85        (Type::NonNullType(of_type), value) => ast_from_value(ctx, value, of_type),
86
87        (Type::NamedType(NamedType { name: "Boolean" }), JSValue::Bool(x)) => {
88            Ok(Value::Boolean((*x).into()))
89        }
90
91        (Type::NamedType(NamedType { name: "Boolean" }), JSValue::Number(num)) => {
92            Ok(Value::Boolean((num.as_u64().unwrap_or(0) != 0).into()))
93        }
94
95        (Type::NamedType(NamedType { name: "Int" }), JSValue::Number(num)) => num
96            .as_i64()
97            .map(|x| {
98                Value::Int(IntValue {
99                    value: ctx.alloc_str(&x.to_string()),
100                })
101            })
102            .ok_or_else(|| Error::new("Received Float for Int type", None)),
103
104        (Type::NamedType(NamedType { name: "Float" }), JSValue::Number(num)) => {
105            let num = num.as_f64().unwrap_or(0.0);
106            if num.is_finite() {
107                Ok(Value::Float(FloatValue {
108                    value: ctx.alloc_str(&num.to_string()),
109                }))
110            } else {
111                Err(Error::new("Received non-finite Float for Float type", None))
112            }
113        }
114
115        (
116            Type::NamedType(NamedType {
117                name: "ID" | "String",
118            }),
119            JSValue::String(str),
120        ) => Ok(Value::String(ctx.alloc_str(str).into())),
121
122        (
123            Type::NamedType(NamedType {
124                name: "ID" | "String",
125            }),
126            JSValue::Number(num),
127        ) => Ok(Value::String(ctx.alloc_string(num.to_string()).into())),
128
129        (Type::NamedType(NamedType { name: _ }), value) => Ok(ast_from_value_untyped(ctx, value)),
130    }
131}
132
133/// Convert [serde_json::Value] to an AST Value Node without casting the JSON value to a type.
134pub fn ast_from_value_untyped<'a>(ctx: &'a ASTContext, value: &JSValue) -> Value<'a> {
135    match value {
136        JSValue::Array(list) => {
137            let new_list = list.iter().map(|value| ast_from_value_untyped(ctx, value));
138
139            let mut new_list_children = Vec::new_in(&ctx.arena);
140            for item in new_list {
141                new_list_children.push(item);
142            }
143
144            return Value::List(ListValue {
145                children: new_list_children,
146            });
147        }
148        JSValue::Object(map) => {
149            let new_object_children_iter = map.iter().map(|(key, value)| ObjectField {
150                name: ctx.alloc_str(key),
151                value: ast_from_value_untyped(ctx, value),
152            });
153            let mut new_object_children = Vec::new_in(&ctx.arena);
154            for item in new_object_children_iter {
155                new_object_children.push(item);
156            }
157            return Value::Object(ObjectValue {
158                children: new_object_children,
159            });
160        }
161        JSValue::Number(num) => num
162            .as_i64()
163            .map(|x| {
164                Value::Int(IntValue {
165                    value: ctx.alloc_str(&x.to_string()),
166                })
167            })
168            .unwrap_or_else(|| {
169                let float = num.as_f64().filter(|x| x.is_finite()).unwrap_or(0.0);
170                Value::Float(FloatValue {
171                    value: ctx.alloc_str(&float.to_string()),
172                })
173            }),
174        JSValue::Bool(x) => Value::Boolean((*x).into()),
175        JSValue::String(str) => Value::String(ctx.alloc_str(str).into()),
176        JSValue::Null => Value::Null,
177    }
178}
179
180/// Convert [Variables] back to a [serde_json::Value].
181pub fn value_from_ast_variables<'a>(variable: &'a Variables<'a>) -> JSMap<String, JSValue> {
182    let mut map = JSMap::new();
183    for (key, value) in variable.iter() {
184        map.insert(key.to_string(), value.clone().to_json(None));
185    }
186    map
187}
188
189/// Convert AST Value Node back to a [serde_json::Value] given a [Type] definition.
190pub fn value_from_ast<'a>(
191    value: &Value<'a>,
192    of_type: &'a Type<'a>,
193    variables: Option<&'a Variables<'a>>,
194) -> Result<JSValue> {
195    match (of_type, value) {
196        (of_type, Value::Variable(var)) => variables
197            .and_then(|vars| vars.get(var.name))
198            .ok_or_else(|| Error::new("Invalid variable reference when casting to value", None))
199            .and_then(|value| value_from_ast(value, of_type, None)),
200
201        (Type::ListType(of_type), Value::List(list)) => {
202            let new_list_children_iter = list
203                .children
204                .iter()
205                .map(|value| value_from_ast(value, of_type, variables));
206            let mut new_children = vec![];
207            for item in new_list_children_iter {
208                new_children.push(item?);
209            }
210            Ok(JSValue::Array(new_children))
211        }
212
213        (Type::ListType(of_type), value) => {
214            let child = value_from_ast(value, of_type, variables)?;
215            Ok(JSValue::Array(vec![child]))
216        }
217
218        (Type::NonNullType(_), Value::Null) => {
219            Err(Error::new("Received null for non-nullable type", None))
220        }
221
222        (_, Value::Null) => Ok(JSValue::Null),
223
224        (Type::NonNullType(of_type), value) => value_from_ast(value, of_type, variables),
225
226        (Type::NamedType(NamedType { name: "Boolean" }), Value::Boolean(x)) => {
227            Ok(JSValue::Bool(x.value))
228        }
229        (Type::NamedType(NamedType { name: "Boolean" }), Value::Int(x)) => {
230            let res = x.value.parse::<i32>();
231            match res {
232                Ok(int) => Ok(JSValue::Bool(int != 0)),
233                Err(_) => Err(Error::new(
234                    format!("Got invalid Int {} expected Boolean type.", x.value),
235                    None,
236                )),
237            }
238        }
239
240        (Type::NamedType(NamedType { name: "Int" }), Value::Int(x)) => {
241            let res = x.value.parse::<i32>();
242            match res {
243                Ok(int) => Ok(JSValue::Number(int.into())),
244                Err(_) => Err(Error::new(format!("Got invalid Int {}.", x.value), None)),
245            }
246        }
247        (Type::NamedType(NamedType { name: "Float" }), Value::Float(x)) => Ok(json!(x.value)),
248
249        (
250            Type::NamedType(NamedType {
251                name: "ID" | "String",
252            }),
253            Value::Int(num),
254        ) => Ok(JSValue::String(num.value.to_string())),
255
256        (
257            Type::NamedType(NamedType {
258                name: "ID" | "String",
259            }),
260            Value::String(str),
261        ) => Ok(JSValue::String(str.value.into())),
262
263        (Type::NamedType(NamedType { name: _ }), value) => Ok(value.to_owned().to_json(variables)),
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::{ast_variables_from_value, DefaultIn};
270    use crate::ast::{
271        ASTContext, Directives, NamedType, Type, Value, Variable, VariableDefinition,
272        VariableDefinitions,
273    };
274    use bumpalo::collections::Vec;
275    use serde_json::{json, Value as JsValue};
276
277    #[test]
278    fn nullable_list() {
279        let ctx = ASTContext::new();
280        let input = json!({
281            "list": JsValue::Null,
282        });
283
284        let var = vec![VariableDefinition {
285            variable: Variable { name: "list" },
286            of_type: Type::ListType(&Type::NonNullType(&Type::NamedType(NamedType {
287                name: "Int",
288            }))),
289            default_value: Value::Null,
290            directives: Directives::default_in(&ctx.arena)
291        }];
292
293        let var_defs = VariableDefinitions {
294            children: Vec::from_iter_in(var, &ctx.arena),
295        };
296        let _ = ast_variables_from_value(&ctx, &input, &var_defs).unwrap();
297    }
298
299    #[test]
300    fn object_list() {
301        let ctx = ASTContext::new();
302        let input = json!({
303            "orderBys": [{
304                "equals": {
305                    "value": 5
306                }
307            }],
308        });
309
310        let var = vec![VariableDefinition {
311            variable: Variable { name: "orderBys" },
312            of_type: Type::ListType(&Type::NamedType(NamedType {
313                name: "orderByInput",
314            })),
315            default_value: Value::Null,
316            directives: Directives::default_in(&ctx.arena)
317        }];
318
319        let var_defs = VariableDefinitions {
320            children: Vec::from_iter_in(var, &ctx.arena),
321        };
322        let _ = ast_variables_from_value(&ctx, &input, &var_defs).unwrap();
323    }
324}