cynic/queries/
ast.rs

1use std::{borrow::Cow, fmt::Write};
2
3use super::indent::indented;
4
5#[derive(Debug, Default)]
6/// A set of field selections that form part of a graphql query.
7pub struct SelectionSet {
8    pub(super) selections: Vec<Selection>,
9}
10
11#[derive(Debug)]
12/// An individual selection
13pub enum Selection {
14    /// Selects a field
15    Field(FieldSelection),
16    /// Selects an inline fragment
17    InlineFragment(InlineFragment),
18}
19
20#[derive(Debug)]
21/// The details of a particular field selection
22pub struct FieldSelection {
23    pub(super) name: &'static str,
24    pub(super) alias: Option<Cow<'static, str>>,
25    pub(super) arguments: Vec<Argument>,
26    pub(super) directives: Vec<Directive>,
27    pub(super) children: SelectionSet,
28}
29
30#[derive(Debug, PartialEq)]
31/// An argument
32pub struct Argument {
33    pub(super) name: Cow<'static, str>,
34    pub(super) value: InputLiteral,
35}
36
37impl Argument {
38    /// Constructs an `Argument`
39    pub fn new(name: &'static str, value: InputLiteral) -> Self {
40        Argument {
41            name: Cow::Borrowed(name),
42            value,
43        }
44    }
45
46    /// Constructs an `Argument` with a `Cow` as its name
47    pub fn from_cow_name(name: Cow<'static, str>, value: InputLiteral) -> Self {
48        Argument { name, value }
49    }
50}
51
52#[derive(Debug, PartialEq)]
53/// An `InputLiteral` is an argument that will be output in the GraphQL
54/// query text (as opposed to a variable that will go in the variables
55/// field)
56pub enum InputLiteral {
57    /// An integer
58    Int(i32),
59    /// A float
60    Float(f64),
61    /// A boolean
62    Bool(bool),
63    /// A string
64    String(Cow<'static, str>),
65    /// An ID
66    Id(String),
67    /// An object
68    Object(Vec<Argument>),
69    /// A list
70    List(Vec<InputLiteral>),
71    /// A variable
72    Variable(&'static str),
73    /// A null
74    Null,
75    /// One of the values of an enum
76    EnumValue(&'static str),
77}
78
79#[derive(Debug, PartialEq)]
80/// A directive
81pub struct Directive {
82    pub(super) name: Cow<'static, str>,
83    pub(super) arguments: Vec<Argument>,
84}
85
86#[derive(Debug, Default)]
87/// An inline fragment that selects fields from one possible type
88pub struct InlineFragment {
89    pub(super) on_clause: Option<&'static str>,
90    pub(super) children: SelectionSet,
91}
92
93impl FieldSelection {
94    /// Creates a new FieldSelection
95    pub fn new(name: &'static str) -> FieldSelection {
96        FieldSelection {
97            name,
98            alias: None,
99            arguments: Vec::new(),
100            directives: Vec::new(),
101            children: SelectionSet::default(),
102        }
103    }
104}
105
106impl std::fmt::Display for SelectionSet {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        if !self.selections.is_empty() {
109            writeln!(f, " {{")?;
110            for child in &self.selections {
111                write!(indented(f, 2), "{}", child)?;
112            }
113            write!(f, "}}")?;
114        }
115        writeln!(f)
116    }
117}
118
119impl std::fmt::Display for Selection {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        match self {
122            Selection::Field(field_selection) => {
123                if let Some(alias) = &field_selection.alias {
124                    write!(f, "{}: ", alias)?;
125                }
126
127                write!(f, "{}", field_selection.name)?;
128
129                if !field_selection.arguments.is_empty() {
130                    write!(f, "(")?;
131                    let mut first = true;
132                    for arg in &field_selection.arguments {
133                        if !first {
134                            write!(f, ", ")?;
135                        }
136                        first = false;
137                        write!(f, "{}", arg)?;
138                    }
139                    write!(f, ")")?;
140                }
141
142                for Directive { name, arguments } in &field_selection.directives {
143                    write!(f, " @{name}")?;
144                    if !arguments.is_empty() {
145                        write!(f, "(")?;
146                        let mut first = true;
147                        for arg in arguments {
148                            if !first {
149                                write!(f, ", ")?;
150                            }
151                            first = false;
152                            write!(f, "{}", arg)?;
153                        }
154                        write!(f, ")")?;
155                    }
156                }
157                write!(f, "{}", field_selection.children)
158            }
159            Selection::InlineFragment(inline_fragment) => {
160                // Don't print any empty fragments - this can happen in recursive queries...
161                if !inline_fragment.children.selections.is_empty() {
162                    write!(f, "...")?;
163                    if let Some(on_type) = inline_fragment.on_clause {
164                        write!(f, " on {}", on_type)?;
165                    }
166                    write!(f, "{}", inline_fragment.children)?;
167                }
168                Ok(())
169            }
170        }
171    }
172}
173
174impl std::fmt::Display for Argument {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        write!(f, "{}: {}", self.name, self.value)
177    }
178}
179
180impl std::fmt::Display for InputLiteral {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        match self {
183            InputLiteral::Int(val) => write!(f, "{}", val),
184            InputLiteral::Float(val) => write!(f, "{}", val),
185            InputLiteral::Bool(val) => write!(f, "{}", val),
186            InputLiteral::String(val) => {
187                let val = escape_string(val);
188                write!(f, "\"{val}\"")
189            }
190            InputLiteral::Id(val) => write!(f, "\"{}\"", val),
191            InputLiteral::Object(fields) => {
192                write!(f, "{{")?;
193                for (i, field) in fields.iter().enumerate() {
194                    if i != 0 {
195                        write!(f, ", ")?;
196                    }
197                    write!(f, "{}: {}", field.name, field.value)?;
198                }
199                write!(f, "}}")
200            }
201            InputLiteral::List(vals) => {
202                write!(f, "[")?;
203                for (i, val) in vals.iter().enumerate() {
204                    if i != 0 {
205                        write!(f, ", ")?;
206                    }
207                    write!(f, "{}", val)?;
208                }
209                write!(f, "]")
210            }
211            InputLiteral::Variable(name) => {
212                write!(f, "${}", name)
213            }
214            InputLiteral::Null => {
215                write!(f, "null")
216            }
217            InputLiteral::EnumValue(name) => {
218                write!(f, "{name}")
219            }
220        }
221    }
222}
223
224fn escape_string(src: &str) -> String {
225    let mut dest = String::with_capacity(src.len());
226
227    for character in src.chars() {
228        match character {
229            '"' | '\\' | '\n' | '\r' | '\t' => {
230                dest.extend(character.escape_default());
231            }
232            other if other.is_control() => {
233                dest.extend(character.escape_default());
234            }
235            _ => dest.push(character),
236        }
237    }
238
239    dest
240}