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}