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 pub fn write_sdl(&self, f: &mut dyn Write) -> std::fmt::Result {
13 write!(f, "{}", SchemaDisplay(self))
14 }
15
16 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 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 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 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 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 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}