async_graphql/registry/
stringify_exec_doc.rs

1use std::fmt::{Error, Result as FmtResult, Write};
2
3use async_graphql_value::ConstValue;
4
5use crate::{
6    parser::types::{
7        ExecutableDocument, FragmentDefinition, OperationType, Selection, SelectionSet,
8    },
9    registry::{MetaInputValue, MetaType, MetaTypeName, Registry},
10    Variables,
11};
12
13impl Registry {
14    pub(crate) fn stringify_exec_doc(
15        &self,
16        variables: &Variables,
17        doc: &ExecutableDocument,
18    ) -> Result<String, Error> {
19        let mut output = String::new();
20        for (name, fragment) in &doc.fragments {
21            self.stringify_fragment_definition(
22                &mut output,
23                variables,
24                name,
25                self.types
26                    .get(fragment.node.type_condition.node.on.node.as_str()),
27                &fragment.node,
28            )?;
29        }
30        for (name, operation_definition) in doc.operations.iter() {
31            write!(&mut output, "{} ", operation_definition.node.ty)?;
32            if let Some(name) = name {
33                write!(&mut output, "{}", name)?;
34
35                if !operation_definition.node.variable_definitions.is_empty() {
36                    output.push('(');
37                    for (idx, variable_definition) in operation_definition
38                        .node
39                        .variable_definitions
40                        .iter()
41                        .enumerate()
42                    {
43                        if idx > 0 {
44                            output.push_str(", ");
45                        }
46                        write!(
47                            output,
48                            "${}: {}",
49                            variable_definition.node.name.node,
50                            variable_definition.node.var_type.node
51                        )?;
52                        if let Some(default_value) = &variable_definition.node.default_value {
53                            write!(output, " = {}", default_value.node)?;
54                        }
55                    }
56                    output.push(')');
57                }
58
59                output.push(' ');
60            }
61            let root_type = match operation_definition.node.ty {
62                OperationType::Query => self.types.get(&self.query_type),
63                OperationType::Mutation => self
64                    .mutation_type
65                    .as_ref()
66                    .and_then(|name| self.types.get(name)),
67                OperationType::Subscription => self
68                    .subscription_type
69                    .as_ref()
70                    .and_then(|name| self.types.get(name)),
71            };
72            self.stringify_selection_set(
73                &mut output,
74                variables,
75                &operation_definition.node.selection_set.node,
76                root_type,
77            )?;
78        }
79        Ok(output)
80    }
81
82    fn stringify_fragment_definition(
83        &self,
84        output: &mut String,
85        variables: &Variables,
86        name: &str,
87        parent_type: Option<&MetaType>,
88        fragment_definition: &FragmentDefinition,
89    ) -> FmtResult {
90        write!(
91            output,
92            "fragment {} on {}",
93            name, fragment_definition.type_condition.node.on.node
94        )?;
95        self.stringify_selection_set(
96            output,
97            variables,
98            &fragment_definition.selection_set.node,
99            parent_type,
100        )?;
101        output.push_str("}\n\n");
102        Ok(())
103    }
104
105    fn stringify_input_value(
106        &self,
107        output: &mut String,
108        meta_input_value: Option<&MetaInputValue>,
109        value: &ConstValue,
110    ) -> FmtResult {
111        if meta_input_value.map(|v| v.is_secret).unwrap_or_default() {
112            output.push_str("\"<secret>\"");
113            return Ok(());
114        }
115
116        match value {
117            ConstValue::Object(obj) => {
118                let parent_type = meta_input_value.and_then(|input_value| {
119                    self.types
120                        .get(MetaTypeName::concrete_typename(&input_value.ty))
121                });
122                if let Some(MetaType::InputObject { input_fields, .. }) = parent_type {
123                    output.push('{');
124                    for (idx, (key, value)) in obj.iter().enumerate() {
125                        if idx > 0 {
126                            output.push_str(", ");
127                        }
128                        write!(output, "{}: ", key)?;
129                        self.stringify_input_value(output, input_fields.get(key.as_str()), value)?;
130                    }
131                    output.push('}');
132                } else {
133                    write!(output, "{}", value)?;
134                }
135            }
136            _ => write!(output, "{}", value)?,
137        }
138
139        Ok(())
140    }
141
142    fn stringify_selection_set(
143        &self,
144        output: &mut String,
145        variables: &Variables,
146        selection_set: &SelectionSet,
147        parent_type: Option<&MetaType>,
148    ) -> FmtResult {
149        output.push_str("{ ");
150        for (idx, selection) in selection_set.items.iter().map(|s| &s.node).enumerate() {
151            if idx > 0 {
152                output.push(' ');
153            }
154            match selection {
155                Selection::Field(field) => {
156                    if let Some(alias) = &field.node.alias {
157                        write!(output, "{}:", alias.node)?;
158                    }
159                    write!(output, "{}", field.node.name.node)?;
160                    if !field.node.arguments.is_empty() {
161                        output.push('(');
162                        for (idx, (name, argument)) in field.node.arguments.iter().enumerate() {
163                            let meta_input_value = parent_type
164                                .and_then(|parent_type| {
165                                    parent_type.field_by_name(field.node.name.node.as_str())
166                                })
167                                .and_then(|field| field.args.get(name.node.as_str()));
168                            if idx > 0 {
169                                output.push_str(", ");
170                            }
171                            write!(output, "{}: ", name)?;
172                            let value = argument
173                                .node
174                                .clone()
175                                .into_const_with(|name| variables.get(&name).cloned().ok_or(()))
176                                .unwrap_or_default();
177                            self.stringify_input_value(output, meta_input_value, &value)?;
178                        }
179                        output.push(')');
180                    }
181                    if !field.node.selection_set.node.items.is_empty() {
182                        output.push(' ');
183                        let parent_type = parent_type
184                            .and_then(|ty| ty.field_by_name(field.node.name.node.as_str()))
185                            .and_then(|field| {
186                                self.types.get(MetaTypeName::concrete_typename(&field.ty))
187                            });
188                        self.stringify_selection_set(
189                            output,
190                            variables,
191                            &field.node.selection_set.node,
192                            parent_type,
193                        )?;
194                    }
195                }
196                Selection::FragmentSpread(fragment_spread) => {
197                    write!(output, "... {}", fragment_spread.node.fragment_name.node)?;
198                }
199                Selection::InlineFragment(inline_fragment) => {
200                    output.push_str("... ");
201                    let parent_type = if let Some(name) = &inline_fragment.node.type_condition {
202                        write!(output, "on {} ", name.node.on.node)?;
203                        self.types.get(name.node.on.node.as_str())
204                    } else {
205                        None
206                    };
207                    self.stringify_selection_set(
208                        output,
209                        variables,
210                        &inline_fragment.node.selection_set.node,
211                        parent_type,
212                    )?;
213                }
214            }
215        }
216        output.push_str(" }");
217        Ok(())
218    }
219}
220
221#[cfg(test)]
222#[allow(clippy::diverging_sub_expression)]
223mod tests {
224    use super::*;
225    use crate::{parser::parse_query, *};
226
227    #[test]
228    fn test_stringify() {
229        let registry = Registry::default();
230        let doc = parse_query(
231            r#"
232            query Abc {
233              a b c(a:1,b:2) {
234                d e f
235              }
236            }
237        "#,
238        )
239        .unwrap();
240        assert_eq!(
241            registry
242                .stringify_exec_doc(&Default::default(), &doc)
243                .unwrap(),
244            r#"query Abc { a b c(a: 1, b: 2) { d e f } }"#
245        );
246
247        let doc = parse_query(
248            r#"
249            query Abc($a:Int) {
250              value(input:$a)
251            }
252        "#,
253        )
254        .unwrap();
255        assert_eq!(
256            registry
257                .stringify_exec_doc(
258                    &Variables::from_value(value! ({
259                        "a": 10,
260                    })),
261                    &doc
262                )
263                .unwrap(),
264            r#"query Abc($a: Int) { value(input: 10) }"#
265        );
266    }
267
268    #[test]
269    fn test_stringify_secret() {
270        #[derive(InputObject)]
271        #[graphql(internal)]
272        struct MyInput {
273            v1: i32,
274            #[graphql(secret)]
275            v2: i32,
276            v3: MyInput2,
277        }
278
279        #[derive(InputObject)]
280        #[graphql(internal)]
281        struct MyInput2 {
282            v4: i32,
283            #[graphql(secret)]
284            v5: i32,
285        }
286
287        struct Query;
288
289        #[Object(internal)]
290        #[allow(unreachable_code, unused_variables)]
291        impl Query {
292            async fn value(&self, a: i32, #[graphql(secret)] b: i32, c: MyInput) -> i32 {
293                todo!()
294            }
295        }
296
297        let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
298        let registry = schema.registry();
299        let s = registry
300            .stringify_exec_doc(
301                &Default::default(),
302                &parse_query(
303                    r#"
304            {
305                value(a: 10, b: 20, c: { v1: 1, v2: 2, v3: { v4: 4, v5: 5}})
306            }
307        "#,
308                )
309                .unwrap(),
310            )
311            .unwrap();
312        assert_eq!(
313            s,
314            r#"query { value(a: 10, b: "<secret>", c: {v1: 1, v2: "<secret>", v3: {v4: 4, v5: "<secret>"}}) }"#
315        );
316    }
317}