cynic_introspection/schema/
sdl.rs

1use std::fmt::{self, Display, Write};
2
3use crate::{
4    Deprecated, DirectiveLocation, EnumType, EnumValue, Field, FieldType, InputObjectType,
5    InputValue, InterfaceType, ObjectType, ScalarType, Type, UnionType,
6};
7
8use super::{Directive, Schema};
9
10impl Schema {
11    /// Writes the SDL for this schema into `f`
12    pub fn write_sdl(&self, f: &mut dyn Write) -> std::fmt::Result {
13        write!(f, "{}", SchemaDisplay(self))
14    }
15
16    /// Returns the SDL for this schema as a string
17    pub fn to_sdl(&self) -> String {
18        let mut output = String::new();
19        self.write_sdl(&mut output)
20            .expect("Writing to a string shouldn't fail");
21        output
22    }
23}
24
25struct SchemaDisplay<'a>(&'a Schema);
26
27impl Display for SchemaDisplay<'_> {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        let schema = &self.0;
30        if self.should_should_schema_definition() {
31            writeln!(f, "schema {{")?;
32            let f2 = &mut indented(f);
33            writeln!(f2, "query: {}", schema.query_type)?;
34            if let Some(mutation_type) = &schema.mutation_type {
35                writeln!(f2, "mutation: {}", mutation_type)?;
36            }
37            if let Some(subscription_type) = &schema.subscription_type {
38                writeln!(f2, "subscription: {}", subscription_type)?;
39            }
40            writeln!(f, "}}")?;
41        }
42
43        for ty in &schema.types {
44            let ty = TypeDisplay(ty);
45            write!(f, "{ty}")?;
46        }
47
48        for directive in &schema.directives {
49            if ["skip", "include", "deprecated", "specifiedBy"].contains(&directive.name.as_str()) {
50                // Skip built in directives, we don't need those...
51                continue;
52            }
53            write!(f, "{}", DirectiveDisplay(directive))?;
54        }
55
56        Ok(())
57    }
58}
59
60impl SchemaDisplay<'_> {
61    fn should_should_schema_definition(&self) -> bool {
62        self.0.query_type != "Query"
63            || self
64                .0
65                .mutation_type
66                .as_ref()
67                .map(|mutation_type| mutation_type != "Mutation")
68                .unwrap_or_default()
69            || self
70                .0
71                .subscription_type
72                .as_ref()
73                .map(|subscription_type| subscription_type != "Subscription")
74                .unwrap_or_default()
75    }
76}
77
78struct TypeDisplay<'a>(&'a Type);
79
80impl std::fmt::Display for TypeDisplay<'_> {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        let ty = self.0;
83
84        if ty.name().starts_with("__") {
85            // Don't render introspection types.
86            return Ok(());
87        }
88
89        match ty {
90            Type::Scalar(
91                scalar @ ScalarType {
92                    name,
93                    description,
94                    specified_by_url,
95                },
96            ) => {
97                if scalar.is_builtin() {
98                    // Don't render built in scalars
99                    return Ok(());
100                }
101                write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
102                writeln!(
103                    f,
104                    "scalar {name}{}\n",
105                    SpecifiedByDisplay(specified_by_url.as_deref())
106                )
107            }
108            Type::Object(ObjectType {
109                name,
110                description,
111                fields,
112                interfaces,
113            }) => {
114                write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
115                if fields.is_empty() {
116                    writeln!(f, "type {name}{}", ImplementsDisplay(interfaces))
117                } else {
118                    writeln!(f, "type {name}{} {{", ImplementsDisplay(interfaces))?;
119                    for field in fields {
120                        writeln!(indented(f), "{}", FieldDisplay(field))?;
121                    }
122                    writeln!(f, "}}\n")
123                }
124            }
125            Type::InputObject(InputObjectType {
126                name,
127                description,
128                fields,
129            }) => {
130                write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
131                if fields.is_empty() {
132                    writeln!(f, "input {name}")
133                } else {
134                    writeln!(f, "input {name} {{")?;
135                    for field in fields {
136                        writeln!(indented(f), "{}", InputValueDisplay(field))?;
137                    }
138                    writeln!(f, "}}\n")
139                }
140            }
141            Type::Enum(EnumType {
142                name,
143                description,
144                values,
145            }) => {
146                write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
147                if values.is_empty() {
148                    writeln!(f, "enum {name}")
149                } else {
150                    writeln!(f, "enum {name} {{")?;
151                    for value in values {
152                        write!(indented(f), "{}", EnumValueDisplay(value))?;
153                    }
154                    writeln!(f, "}}\n")
155                }
156            }
157            Type::Interface(InterfaceType {
158                name,
159                description,
160                fields,
161                interfaces,
162                possible_types: _,
163            }) => {
164                write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
165                if fields.is_empty() {
166                    writeln!(f, "interface {name}{}", ImplementsDisplay(interfaces))
167                } else {
168                    writeln!(f, "interface {name}{} {{", ImplementsDisplay(interfaces))?;
169                    for field in fields {
170                        writeln!(indented(f), "{}", FieldDisplay(field))?;
171                    }
172                    writeln!(f, "}}\n")
173                }
174            }
175            Type::Union(union) => {
176                write!(f, "{}", UnionDisplay(union))
177            }
178        }
179    }
180}
181
182struct UnionDisplay<'a>(&'a UnionType);
183
184impl Display for UnionDisplay<'_> {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        let UnionType {
187            name,
188            description,
189            possible_types,
190        } = self.0;
191        write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
192        let wrap = possible_types.iter().map(String::len).sum::<usize>() > 80;
193
194        write!(f, "union {name} =")?;
195        if wrap {
196            write!(f, "\n   ")?;
197        }
198        for (i, ty) in possible_types.iter().enumerate() {
199            if i != 0 {
200                if wrap {
201                    write!(f, "\n ")?;
202                }
203                write!(f, " |")?;
204            }
205            write!(f, " {ty}")?;
206        }
207        writeln!(f, "\n")
208    }
209}
210
211struct DescriptionDisplay<'a>(Option<&'a str>);
212
213impl Display for DescriptionDisplay<'_> {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        if let Some(description) = self.0 {
216            writeln!(f, r#"""""#)?;
217            writeln!(f, "{description}")?;
218            writeln!(f, r#"""""#)?;
219        }
220
221        Ok(())
222    }
223}
224
225struct FieldDisplay<'a>(&'a Field);
226
227impl Display for FieldDisplay<'_> {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        let Field {
230            name,
231            description,
232            args,
233            ty,
234            deprecated,
235        } = self.0;
236        write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
237        write!(f, "{name}")?;
238        if !args.is_empty() {
239            let wrap = self.should_wrap_arguments();
240            if wrap {
241                writeln!(f, "(")?;
242                for (i, arg) in args.iter().enumerate() {
243                    if i != 0 {
244                        writeln!(f)?;
245                    }
246                    write!(indented(f), "{}", InputValueDisplay(arg))?;
247                }
248                write!(f, "\n)")?;
249            } else {
250                write!(f, "(")?;
251                for (i, arg) in args.iter().enumerate() {
252                    if i != 0 {
253                        write!(f, ", ")?;
254                    }
255                    write!(f, "{}", InputValueDisplay(arg))?;
256                }
257                write!(f, ")")?;
258            }
259        }
260        write!(
261            f,
262            ": {}{}",
263            FieldTypeDisplay(ty),
264            DeprecatedDisplay(deprecated)
265        )?;
266
267        Ok(())
268    }
269}
270
271impl FieldDisplay<'_> {
272    /// Hacky heuristic for whether we should wrap the field arguments
273    fn should_wrap_arguments(&self) -> bool {
274        if self.0.args.iter().any(|arg| arg.description.is_some()) {
275            return true;
276        }
277        let arg_len = self
278            .0
279            .args
280            .iter()
281            .map(|arg| arg.name.len() + arg.ty.name.len())
282            .sum::<usize>();
283        arg_len + self.0.ty.name.len() > 80
284    }
285}
286
287struct ImplementsDisplay<'a>(&'a [String]);
288
289impl Display for ImplementsDisplay<'_> {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        let interfaces = &self.0;
292        if interfaces.is_empty() {
293            return Ok(());
294        }
295        write!(f, " implements ")?;
296        for (i, interface) in interfaces.iter().enumerate() {
297            if i != 0 {
298                write!(f, " & ")?;
299            }
300            write!(f, "{interface}")?;
301        }
302        Ok(())
303    }
304}
305
306struct FieldTypeDisplay<'a>(&'a FieldType);
307
308impl Display for FieldTypeDisplay<'_> {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        write!(f, "{}", self.0)
311    }
312}
313
314struct InputValueDisplay<'a>(&'a InputValue);
315
316impl Display for InputValueDisplay<'_> {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        let InputValue {
319            name,
320            description,
321            ty,
322            default_value,
323        } = self.0;
324        write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
325        write!(f, "{name}: {}", FieldTypeDisplay(ty))?;
326        if let Some(default_value) = default_value {
327            write!(f, " = {}", default_value)?;
328        }
329        Ok(())
330    }
331}
332
333struct EnumValueDisplay<'a>(&'a EnumValue);
334
335impl Display for EnumValueDisplay<'_> {
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        let EnumValue {
338            name,
339            description,
340            deprecated,
341        } = self.0;
342        write!(f, "{}", DescriptionDisplay(description.as_deref()))?;
343        write!(f, "{name}")?;
344        writeln!(f, "{}", DeprecatedDisplay(deprecated))
345    }
346}
347
348struct DeprecatedDisplay<'a>(&'a Deprecated);
349
350impl Display for DeprecatedDisplay<'_> {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        match self.0 {
353            Deprecated::No => {}
354            Deprecated::Yes(None) => write!(f, " @deprecated")?,
355            Deprecated::Yes(Some(reason)) => {
356                write!(f, " @deprecated(reason: \"{}\")", escape_string(reason))?
357            }
358        }
359        Ok(())
360    }
361}
362
363struct SpecifiedByDisplay<'a>(Option<&'a str>);
364
365impl Display for SpecifiedByDisplay<'_> {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        match self.0 {
368            None => {}
369            Some(url) => write!(f, " @specifiedBy(url: {url})")?,
370        }
371        Ok(())
372    }
373}
374
375struct DirectiveDisplay<'a>(&'a Directive);
376
377impl Display for DirectiveDisplay<'_> {
378    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379        let name = &self.0.name;
380
381        write!(f, "{}", DescriptionDisplay(self.0.description.as_deref()))?;
382        write!(f, "directive @{name}")?;
383        if !self.0.args.is_empty() {
384            let wrap = self.should_wrap_arguments();
385            if wrap {
386                writeln!(f, "(")?;
387                for (i, arg) in self.0.args.iter().enumerate() {
388                    if i != 0 {
389                        writeln!(f)?;
390                    }
391                    write!(indented(f), "{}", InputValueDisplay(arg))?;
392                }
393                write!(f, "\n)")?;
394            } else {
395                write!(f, "(")?;
396                for (i, arg) in self.0.args.iter().enumerate() {
397                    if i != 0 {
398                        write!(f, ", ")?;
399                    }
400                    write!(f, "{}", InputValueDisplay(arg))?;
401                }
402                write!(f, ")")?;
403            }
404        }
405        if self.0.is_repeatable {
406            write!(f, " repeatable")?;
407        }
408        write!(f, " on ")?;
409        for (i, location) in self.0.locations.iter().enumerate() {
410            if i != 0 {
411                write!(f, " | ")?;
412            }
413            write!(f, "{}", location.as_str())?;
414        }
415        writeln!(f, "\n")?;
416        Ok(())
417    }
418}
419
420impl DirectiveDisplay<'_> {
421    /// Hacky heuristic for whether we should wrap the field arguments
422    fn should_wrap_arguments(&self) -> bool {
423        if self.0.args.iter().any(|arg| arg.description.is_some()) {
424            return true;
425        }
426        let arg_len = self
427            .0
428            .args
429            .iter()
430            .map(|arg| arg.name.len() + arg.ty.name.len())
431            .sum::<usize>();
432        arg_len + self.0.name.len() > 80
433    }
434}
435
436impl DirectiveLocation {
437    fn as_str(self) -> &'static str {
438        match self {
439            DirectiveLocation::Query => "QUERY",
440            DirectiveLocation::Mutation => "MUTATION",
441            DirectiveLocation::Subscription => "SUBSCRIPTION",
442            DirectiveLocation::Field => "FIELD",
443            DirectiveLocation::FragmentDefinition => "FRAGMENT_DEFINITION",
444            DirectiveLocation::FragmentSpread => "FRAGMENT_SPREAD",
445            DirectiveLocation::InlineFragment => "INLINE_FRAGMENT",
446            DirectiveLocation::VariableDefinition => "VARIABLE_DEFINITION",
447            DirectiveLocation::Schema => "SCHEMA",
448            DirectiveLocation::Scalar => "SCALAR",
449            DirectiveLocation::Object => "OBJECT",
450            DirectiveLocation::FieldDefinition => "FIELD_DEFINITION",
451            DirectiveLocation::ArgumentDefinition => "ARGUMENT_DEFINITION",
452            DirectiveLocation::Interface => "INTERFACE",
453            DirectiveLocation::Union => "UNION",
454            DirectiveLocation::Enum => "ENUM",
455            DirectiveLocation::EnumValue => "ENUM_VALUE",
456            DirectiveLocation::InputObject => "INPUT_OBJECT",
457            DirectiveLocation::InputFieldDefinition => "INPUT_FIELD_DEFINITION",
458        }
459    }
460}
461
462pub fn indented<D>(f: &mut D) -> indenter::Indented<'_, D> {
463    indenter::indented(f).with_str("  ")
464}
465
466fn escape_string(src: &str) -> String {
467    let mut dest = String::with_capacity(src.len());
468
469    for character in src.chars() {
470        match character {
471            '"' | '\\' | '\n' | '\r' | '\t' => {
472                dest.extend(character.escape_default());
473            }
474            _ => dest.push(character),
475        }
476    }
477
478    dest
479}