Skip to main content

robinpath_modules/modules/
graphql_mod.rs

1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4    // graphql.buildQuery(queryName, fields, variables?) -> query string
5    rp.register_builtin("graphql.buildQuery", |args, _| {
6        let query_name = args.first().map(|v| v.to_display_string()).unwrap_or_default();
7        let fields = args.get(1);
8        let variables = args.get(2);
9
10        let fields_str = format_fields(fields);
11
12        let vars_decl = if let Some(vars) = variables {
13            if let Value::Object(map) = vars {
14                if map.is_empty() {
15                    String::new()
16                } else {
17                    let params: Vec<String> = map
18                        .iter()
19                        .map(|(k, v)| format!("${}: {}", k, graphql_type_of(v)))
20                        .collect();
21                    let args_inner: Vec<String> = map
22                        .keys()
23                        .map(|k| format!("{}: ${}", k, k))
24                        .collect();
25                    format!(
26                        "({})",
27                        params.join(", ")
28                    ) + &format!(" {{ {}({}) {{ {} }} }}", query_name, args_inner.join(", "), fields_str)
29                }
30            } else {
31                String::new()
32            }
33        } else {
34            String::new()
35        };
36
37        if vars_decl.is_empty() {
38            Ok(Value::String(format!(
39                "query {} {{ {} }}",
40                query_name, fields_str
41            )))
42        } else {
43            Ok(Value::String(format!("query {}{}", query_name, vars_decl)))
44        }
45    });
46
47    // graphql.buildMutation(mutationName, input, fields) -> mutation string
48    rp.register_builtin("graphql.buildMutation", |args, _| {
49        let mutation_name = args.first().map(|v| v.to_display_string()).unwrap_or_default();
50        let input = args.get(1);
51        let fields = args.get(2);
52
53        let fields_str = format_fields(fields);
54        let input_str = format_input(input);
55
56        Ok(Value::String(format!(
57            "mutation {{ {}(input: {}) {{ {} }} }}",
58            mutation_name, input_str, fields_str
59        )))
60    });
61
62    // graphql.parseResponse(responseObj) -> extracts data from {data, errors}
63    rp.register_builtin("graphql.parseResponse", |args, _| {
64        let resp = args.first().cloned().unwrap_or(Value::Null);
65        match resp {
66            Value::Object(map) => {
67                // If there are errors, return the errors
68                if let Some(errors) = map.get("errors") {
69                    if let Value::Array(arr) = errors {
70                        if !arr.is_empty() {
71                            let mut result = indexmap::IndexMap::new();
72                            result.insert("data".to_string(), map.get("data").cloned().unwrap_or(Value::Null));
73                            result.insert("errors".to_string(), Value::Array(arr.clone()));
74                            return Ok(Value::Object(result));
75                        }
76                    }
77                }
78                // Return data field
79                Ok(map.get("data").cloned().unwrap_or(Value::Null))
80            }
81            _ => Ok(Value::Null),
82        }
83    });
84
85    // graphql.extractErrors(responseObj) -> array of error messages or null
86    rp.register_builtin("graphql.extractErrors", |args, _| {
87        let resp = args.first().cloned().unwrap_or(Value::Null);
88        match resp {
89            Value::Object(map) => {
90                if let Some(errors) = map.get("errors") {
91                    if let Value::Array(arr) = errors {
92                        if !arr.is_empty() {
93                            let messages: Vec<Value> = arr
94                                .iter()
95                                .map(|e| {
96                                    if let Value::Object(err_obj) = e {
97                                        err_obj
98                                            .get("message")
99                                            .cloned()
100                                            .unwrap_or_else(|| Value::String(e.to_display_string()))
101                                    } else {
102                                        Value::String(e.to_display_string())
103                                    }
104                                })
105                                .collect();
106                            return Ok(Value::Array(messages));
107                        }
108                    }
109                }
110                Ok(Value::Null)
111            }
112            _ => Ok(Value::Null),
113        }
114    });
115}
116
117fn format_fields(fields: Option<&Value>) -> String {
118    match fields {
119        Some(Value::Array(arr)) => {
120            arr.iter().map(|v| v.to_display_string()).collect::<Vec<_>>().join(" ")
121        }
122        Some(Value::String(s)) => s.clone(),
123        Some(v) => v.to_display_string(),
124        None => String::new(),
125    }
126}
127
128fn format_input(input: Option<&Value>) -> String {
129    match input {
130        Some(Value::Object(map)) => {
131            let pairs: Vec<String> = map
132                .iter()
133                .map(|(k, v)| format!("{}: {}", k, format_graphql_value(v)))
134                .collect();
135            format!("{{ {} }}", pairs.join(", "))
136        }
137        Some(v) => v.to_display_string(),
138        None => "{}".to_string(),
139    }
140}
141
142fn format_graphql_value(v: &Value) -> String {
143    match v {
144        Value::String(s) => format!("\"{}\"", s),
145        Value::Number(n) => {
146            if *n == (*n as i64) as f64 {
147                format!("{}", *n as i64)
148            } else {
149                format!("{}", n)
150            }
151        }
152        Value::Bool(b) => format!("{}", b),
153        Value::Null => "null".to_string(),
154        Value::Array(arr) => {
155            let items: Vec<String> = arr.iter().map(format_graphql_value).collect();
156            format!("[{}]", items.join(", "))
157        }
158        Value::Object(map) => {
159            let pairs: Vec<String> = map
160                .iter()
161                .map(|(k, v)| format!("{}: {}", k, format_graphql_value(v)))
162                .collect();
163            format!("{{ {} }}", pairs.join(", "))
164        }
165        Value::Undefined => "null".to_string(),
166    }
167}
168
169fn graphql_type_of(v: &Value) -> &'static str {
170    match v {
171        Value::String(_) => "String",
172        Value::Number(n) => {
173            if *n == (*n as i64) as f64 {
174                "Int"
175            } else {
176                "Float"
177            }
178        }
179        Value::Bool(_) => "Boolean",
180        _ => "String",
181    }
182}