Skip to main content

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