1use std::{collections::HashMap, fmt::Write};
2
3use crate::registry::{Deprecation, MetaField, MetaInputValue, MetaType, Registry};
4
5const SYSTEM_SCALARS: &[&str] = &["Int", "Float", "String", "Boolean", "ID"];
6const FEDERATION_SCALARS: &[&str] = &["Any"];
7
8#[derive(Debug, Copy, Clone)]
10pub struct SDLExportOptions {
11 sorted_fields: bool,
12 sorted_arguments: bool,
13 sorted_enum_values: bool,
14 federation: bool,
15 prefer_single_line_descriptions: bool,
16 include_specified_by: bool,
17 compose_directive: bool,
18 use_space_ident: bool,
19 indent_width: u8,
20}
21
22impl Default for SDLExportOptions {
23 fn default() -> Self {
24 Self {
25 sorted_fields: false,
26 sorted_arguments: false,
27 sorted_enum_values: false,
28 federation: false,
29 prefer_single_line_descriptions: false,
30 include_specified_by: false,
31 compose_directive: false,
32 use_space_ident: false,
33 indent_width: 2,
34 }
35 }
36}
37
38impl SDLExportOptions {
39 #[inline]
41 pub fn new() -> Self {
42 Default::default()
43 }
44
45 #[inline]
47 #[must_use]
48 pub fn sorted_fields(self) -> Self {
49 Self {
50 sorted_fields: true,
51 ..self
52 }
53 }
54
55 #[inline]
57 #[must_use]
58 pub fn sorted_arguments(self) -> Self {
59 Self {
60 sorted_arguments: true,
61 ..self
62 }
63 }
64
65 #[inline]
67 #[must_use]
68 pub fn sorted_enum_items(self) -> Self {
69 Self {
70 sorted_enum_values: true,
71 ..self
72 }
73 }
74
75 #[inline]
77 #[must_use]
78 pub fn federation(self) -> Self {
79 Self {
80 federation: true,
81 ..self
82 }
83 }
84
85 #[inline]
87 #[must_use]
88 pub fn prefer_single_line_descriptions(self) -> Self {
89 Self {
90 prefer_single_line_descriptions: true,
91 ..self
92 }
93 }
94
95 pub fn include_specified_by(self) -> Self {
97 Self {
98 include_specified_by: true,
99 ..self
100 }
101 }
102
103 pub fn compose_directive(self) -> Self {
105 Self {
106 compose_directive: true,
107 ..self
108 }
109 }
110
111 pub fn use_space_ident(self) -> Self {
113 Self {
114 use_space_ident: true,
115 ..self
116 }
117 }
118
119 pub fn indent_width(self, width: u8) -> Self {
122 Self {
123 indent_width: width,
124 ..self
125 }
126 }
127}
128
129impl Registry {
130 pub(crate) fn export_sdl(&self, options: SDLExportOptions) -> String {
131 let mut sdl = String::new();
132
133 for ty in self.types.values() {
134 if ty.name().starts_with("__") {
135 continue;
136 }
137
138 if options.federation {
139 const FEDERATION_TYPES: &[&str] = &["_Any", "_Entity", "_Service"];
140 if FEDERATION_TYPES.contains(&ty.name()) {
141 continue;
142 }
143 }
144
145 self.export_type(ty, &mut sdl, &options);
146 }
147
148 self.directives.values().for_each(|directive| {
149 if directive.name == "deprecated"
151 && !self.types.values().any(|ty| match ty {
152 MetaType::Object { fields, .. } => fields
153 .values()
154 .any(|field| field.deprecation.is_deprecated()),
155 MetaType::Enum { enum_values, .. } => enum_values
156 .values()
157 .any(|value| value.deprecation.is_deprecated()),
158 _ => false,
159 })
160 {
161 return;
162 }
163
164 if directive.name == "specifiedBy"
166 && !self.types.values().any(|ty| {
167 matches!(
168 ty,
169 MetaType::Scalar {
170 specified_by_url: Some(_),
171 ..
172 }
173 )
174 })
175 {
176 return;
177 }
178
179 if directive.name == "oneOf"
181 && !self
182 .types
183 .values()
184 .any(|ty| matches!(ty, MetaType::InputObject { oneof: true, .. }))
185 {
186 return;
187 }
188
189 writeln!(sdl, "{}", directive.sdl(&options)).ok();
190 });
191
192 if options.federation {
193 writeln!(sdl, "extend schema @link(").ok();
194 writeln!(
195 sdl,
196 "{}url: \"https://specs.apollo.dev/federation/v2.5\",",
197 tab(&options)
198 )
199 .ok();
200 writeln!(sdl, "{}import: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\", \"@requiresScopes\"]", tab(&options)).ok();
201 writeln!(sdl, ")").ok();
202
203 if options.compose_directive {
204 writeln!(sdl).ok();
205 let mut compose_directives = HashMap::<&str, Vec<String>>::new();
206 self.directives
207 .values()
208 .filter_map(|d| {
209 d.composable
210 .as_ref()
211 .map(|ext_url| (ext_url, format!("\"@{}\"", d.name)))
212 })
213 .for_each(|(ext_url, name)| {
214 compose_directives.entry(ext_url).or_default().push(name)
215 });
216 for (url, directives) in compose_directives {
217 writeln!(sdl, "extend schema @link(").ok();
218 writeln!(sdl, "{}url: \"{}\"", tab(&options), url).ok();
219 writeln!(sdl, "{}import: [{}]", tab(&options), directives.join(",")).ok();
220 writeln!(sdl, ")").ok();
221 for name in directives {
222 writeln!(sdl, "{}@composeDirective(name: {})", tab(&options), name).ok();
223 }
224 writeln!(sdl).ok();
225 }
226 }
227 } else {
228 writeln!(sdl, "schema {{").ok();
229 writeln!(sdl, "{}query: {}", tab(&options), self.query_type).ok();
230 if let Some(mutation_type) = self.mutation_type.as_deref() {
231 writeln!(sdl, "{}mutation: {}", tab(&options), mutation_type).ok();
232 }
233 if let Some(subscription_type) = self.subscription_type.as_deref() {
234 writeln!(sdl, "{}subscription: {}", tab(&options), subscription_type).ok();
235 }
236 writeln!(sdl, "}}").ok();
237 }
238
239 sdl
240 }
241
242 fn export_fields<'a, I: Iterator<Item = &'a MetaField>>(
243 sdl: &mut String,
244 it: I,
245 options: &SDLExportOptions,
246 ) {
247 let mut fields = it.collect::<Vec<_>>();
248
249 if options.sorted_fields {
250 fields.sort_by_key(|field| &field.name);
251 }
252
253 for field in fields {
254 if field.name.starts_with("__")
255 || (options.federation && matches!(&*field.name, "_service" | "_entities"))
256 {
257 continue;
258 }
259
260 if let Some(description) = &field.description {
261 write_description(sdl, options, 1, description);
262 }
263
264 if !field.args.is_empty() {
265 write!(sdl, "{}{}(", tab(&options), field.name).ok();
266
267 let mut args = field.args.values().collect::<Vec<_>>();
268 if options.sorted_arguments {
269 args.sort_by_key(|value| &value.name);
270 }
271
272 let need_multiline = args.iter().any(|x| x.description.is_some());
273
274 for (i, arg) in args.into_iter().enumerate() {
275 if i != 0 {
276 sdl.push(',');
277 }
278
279 if let Some(description) = &arg.description {
280 writeln!(sdl).ok();
281 write_description(sdl, options, 2, description);
282 }
283
284 if need_multiline {
285 write!(sdl, "{0}{0}", tab(options)).ok();
286 } else if i != 0 {
287 sdl.push(' ');
288 }
289
290 write_input_value(sdl, arg);
291
292 if options.federation {
293 if arg.inaccessible {
294 write!(sdl, " @inaccessible").ok();
295 }
296
297 for tag in &arg.tags {
298 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
299 }
300 }
301
302 for directive in &arg.directive_invocations {
303 write!(sdl, " {}", directive.sdl()).ok();
304 }
305 }
306
307 if need_multiline {
308 write!(sdl, "\n{}", tab(&options)).ok();
309 }
310 write!(sdl, "): {}", field.ty).ok();
311 } else {
312 write!(sdl, "{}{}: {}", tab(&options), field.name, field.ty).ok();
313 }
314
315 write_deprecated(sdl, &field.deprecation);
316
317 for directive in &field.directive_invocations {
318 write!(sdl, " {}", directive.sdl()).ok();
319 }
320
321 if options.federation {
322 if field.external {
323 write!(sdl, " @external").ok();
324 }
325 if let Some(requires) = &field.requires {
326 write!(sdl, " @requires(fields: \"{}\")", requires).ok();
327 }
328 if let Some(provides) = &field.provides {
329 write!(sdl, " @provides(fields: \"{}\")", provides).ok();
330 }
331 if field.shareable {
332 write!(sdl, " @shareable").ok();
333 }
334 if field.inaccessible {
335 write!(sdl, " @inaccessible").ok();
336 }
337 for tag in &field.tags {
338 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
339 }
340 if let Some(from) = &field.override_from {
341 write!(sdl, " @override(from: \"{}\")", from).ok();
342 }
343
344 if !&field.requires_scopes.is_empty() {
345 write_requires_scopes(sdl, &field.requires_scopes);
346 }
347 }
348
349 writeln!(sdl).ok();
350 }
351 }
352
353 fn export_type(&self, ty: &MetaType, sdl: &mut String, options: &SDLExportOptions) {
354 match ty {
355 MetaType::Scalar {
356 name,
357 description,
358 inaccessible,
359 tags,
360 specified_by_url,
361 directive_invocations,
362 requires_scopes,
363 ..
364 } => {
365 let mut export_scalar = !SYSTEM_SCALARS.contains(&name.as_str());
366 if options.federation && FEDERATION_SCALARS.contains(&name.as_str()) {
367 export_scalar = false;
368 }
369 if export_scalar {
370 if let Some(description) = description {
371 write_description(sdl, options, 0, description);
372 }
373 write!(sdl, "scalar {}", name).ok();
374
375 if options.include_specified_by {
376 if let Some(specified_by_url) = specified_by_url {
377 write!(
378 sdl,
379 " @specifiedBy(url: \"{}\")",
380 specified_by_url.replace('"', "\\\"")
381 )
382 .ok();
383 }
384 }
385
386 if options.federation {
387 if *inaccessible {
388 write!(sdl, " @inaccessible").ok();
389 }
390 for tag in tags {
391 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
392 }
393 if !requires_scopes.is_empty() {
394 write_requires_scopes(sdl, requires_scopes);
395 }
396 }
397
398 for directive in directive_invocations {
399 write!(sdl, " {}", directive.sdl()).ok();
400 }
401
402 writeln!(sdl, "\n").ok();
403 }
404 }
405 MetaType::Object {
406 name,
407 fields,
408 extends,
409 keys,
410 description,
411 shareable,
412 resolvable,
413 inaccessible,
414 interface_object,
415 tags,
416 directive_invocations: raw_directives,
417 requires_scopes,
418 ..
419 } => {
420 if Some(name.as_str()) == self.subscription_type.as_deref()
421 && options.federation
422 && !self.federation_subscription
423 {
424 return;
425 }
426
427 if name.as_str() == self.query_type && options.federation {
428 let mut field_count = 0;
429 for field in fields.values() {
430 if field.name.starts_with("__")
431 || (options.federation
432 && matches!(&*field.name, "_service" | "_entities"))
433 {
434 continue;
435 }
436 field_count += 1;
437 }
438 if field_count == 0 {
439 return;
441 }
442 }
443
444 if let Some(description) = description {
445 write_description(sdl, options, 0, description);
446 }
447
448 if options.federation && *extends {
449 write!(sdl, "extend ").ok();
450 }
451
452 write!(sdl, "type {}", name).ok();
453 self.write_implements(sdl, name);
454
455 for directive_invocation in raw_directives {
456 write!(sdl, " {}", directive_invocation.sdl()).ok();
457 }
458
459 if options.federation {
460 if let Some(keys) = keys {
461 for key in keys {
462 write!(sdl, " @key(fields: \"{}\"", key).ok();
463 if !resolvable {
464 write!(sdl, ", resolvable: false").ok();
465 }
466 write!(sdl, ")").ok();
467 }
468 }
469 if *shareable {
470 write!(sdl, " @shareable").ok();
471 }
472
473 if *inaccessible {
474 write!(sdl, " @inaccessible").ok();
475 }
476
477 if *interface_object {
478 write!(sdl, " @interfaceObject").ok();
479 }
480
481 for tag in tags {
482 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
483 }
484
485 if !requires_scopes.is_empty() {
486 write_requires_scopes(sdl, requires_scopes);
487 }
488 }
489
490 writeln!(sdl, " {{").ok();
491 Self::export_fields(sdl, fields.values(), options);
492 writeln!(sdl, "}}\n").ok();
493 }
494 MetaType::Interface {
495 name,
496 fields,
497 extends,
498 keys,
499 description,
500 inaccessible,
501 tags,
502 directive_invocations,
503 requires_scopes,
504 ..
505 } => {
506 if let Some(description) = description {
507 write_description(sdl, options, 0, description);
508 }
509
510 if options.federation && *extends {
511 write!(sdl, "extend ").ok();
512 }
513 write!(sdl, "interface {}", name).ok();
514
515 if options.federation {
516 if let Some(keys) = keys {
517 for key in keys {
518 write!(sdl, " @key(fields: \"{}\")", key).ok();
519 }
520 }
521 if *inaccessible {
522 write!(sdl, " @inaccessible").ok();
523 }
524
525 for tag in tags {
526 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
527 }
528
529 if !requires_scopes.is_empty() {
530 write_requires_scopes(sdl, requires_scopes);
531 }
532 }
533
534 for directive in directive_invocations {
535 write!(sdl, " {}", directive.sdl()).ok();
536 }
537
538 self.write_implements(sdl, name);
539
540 writeln!(sdl, " {{").ok();
541 Self::export_fields(sdl, fields.values(), options);
542 writeln!(sdl, "}}\n").ok();
543 }
544 MetaType::Enum {
545 name,
546 enum_values,
547 description,
548 inaccessible,
549 tags,
550 directive_invocations,
551 requires_scopes,
552 ..
553 } => {
554 if let Some(description) = description {
555 write_description(sdl, options, 0, description);
556 }
557
558 write!(sdl, "enum {}", name).ok();
559 if options.federation {
560 if *inaccessible {
561 write!(sdl, " @inaccessible").ok();
562 }
563 for tag in tags {
564 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
565 }
566
567 if !requires_scopes.is_empty() {
568 write_requires_scopes(sdl, requires_scopes);
569 }
570 }
571
572 for directive in directive_invocations {
573 write!(sdl, " {}", directive.sdl()).ok();
574 }
575
576 writeln!(sdl, " {{").ok();
577
578 let mut values = enum_values.values().collect::<Vec<_>>();
579 if options.sorted_enum_values {
580 values.sort_by_key(|value| &value.name);
581 }
582
583 for value in values {
584 if let Some(description) = &value.description {
585 write_description(sdl, options, 1, description);
586 }
587 write!(sdl, "{}{}", tab(&options), value.name).ok();
588 write_deprecated(sdl, &value.deprecation);
589
590 if options.federation {
591 if value.inaccessible {
592 write!(sdl, " @inaccessible").ok();
593 }
594
595 for tag in &value.tags {
596 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
597 }
598 }
599
600 for directive in &value.directive_invocations {
601 write!(sdl, " {}", directive.sdl()).ok();
602 }
603
604 writeln!(sdl).ok();
605 }
606
607 writeln!(sdl, "}}\n").ok();
608 }
609 MetaType::InputObject {
610 name,
611 input_fields,
612 description,
613 inaccessible,
614 tags,
615 oneof,
616 directive_invocations: raw_directives,
617 ..
618 } => {
619 if let Some(description) = description {
620 write_description(sdl, options, 0, description);
621 }
622
623 write!(sdl, "input {}", name).ok();
624
625 if *oneof {
626 write!(sdl, " @oneOf").ok();
627 }
628 if options.federation {
629 if *inaccessible {
630 write!(sdl, " @inaccessible").ok();
631 }
632 for tag in tags {
633 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
634 }
635 }
636
637 for directive in raw_directives {
638 write!(sdl, " {}", directive.sdl()).ok();
639 }
640
641 writeln!(sdl, " {{").ok();
642
643 let mut fields = input_fields.values().collect::<Vec<_>>();
644 if options.sorted_fields {
645 fields.sort_by_key(|value| &value.name);
646 }
647
648 for field in fields {
649 if let Some(description) = &field.description {
650 write_description(sdl, options, 1, description);
651 }
652 write!(sdl, "{}", tab(options)).ok();
653 write_input_value(sdl, field);
654 if options.federation {
655 if field.inaccessible {
656 write!(sdl, " @inaccessible").ok();
657 }
658 for tag in &field.tags {
659 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
660 }
661 }
662 for directive in &field.directive_invocations {
663 write!(sdl, " {}", directive.sdl()).ok();
664 }
665 writeln!(sdl).ok();
666 }
667
668 writeln!(sdl, "}}\n").ok();
669 }
670 MetaType::Union {
671 name,
672 possible_types,
673 description,
674 inaccessible,
675 tags,
676 directive_invocations,
677 ..
678 } => {
679 if let Some(description) = description {
680 write_description(sdl, options, 0, description);
681 }
682
683 write!(sdl, "union {}", name).ok();
684 if options.federation {
685 if *inaccessible {
686 write!(sdl, " @inaccessible").ok();
687 }
688 for tag in tags {
689 write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok();
690 }
691 }
692
693 for directive in directive_invocations {
694 write!(sdl, " {}", directive.sdl()).ok();
695 }
696
697 write!(sdl, " =").ok();
698
699 for (idx, ty) in possible_types.iter().enumerate() {
700 if idx == 0 {
701 write!(sdl, " {}", ty).ok();
702 } else {
703 write!(sdl, " | {}", ty).ok();
704 }
705 }
706 writeln!(sdl, "\n").ok();
707 }
708 }
709 }
710
711 fn write_implements(&self, sdl: &mut String, name: &str) {
712 if let Some(implements) = self.implements.get(name) {
713 if !implements.is_empty() {
714 write!(
715 sdl,
716 " implements {}",
717 implements
718 .iter()
719 .map(AsRef::as_ref)
720 .collect::<Vec<&str>>()
721 .join(" & ")
722 )
723 .ok();
724 }
725 }
726 }
727}
728
729pub(super) fn write_description(
730 sdl: &mut String,
731 options: &SDLExportOptions,
732 level: usize,
733 description: &str,
734) {
735 let tabs = tab(options).repeat(level);
736
737 if options.prefer_single_line_descriptions && !description.contains('\n') {
738 let description = description.replace('"', r#"\""#);
739 writeln!(sdl, "{tabs}\"{description}\"").ok();
740 } else {
741 let description = description.replace('\n', &format!("\n{tabs}"));
742 writeln!(sdl, "{tabs}\"\"\"\n{tabs}{description}\n{tabs}\"\"\"").ok();
743 }
744}
745
746fn write_input_value(sdl: &mut String, input_value: &MetaInputValue) {
747 if let Some(default_value) = &input_value.default_value {
748 _ = write!(
749 sdl,
750 "{}: {} = {}",
751 input_value.name, input_value.ty, default_value
752 );
753 } else {
754 _ = write!(sdl, "{}: {}", input_value.name, input_value.ty);
755 }
756
757 write_deprecated(sdl, &input_value.deprecation);
758}
759
760fn write_deprecated(sdl: &mut String, deprecation: &Deprecation) {
761 if let Deprecation::Deprecated { reason } = deprecation {
762 let _ = match reason {
763 Some(reason) => write!(sdl, " @deprecated(reason: \"{}\")", escape_string(reason)).ok(),
764 None => write!(sdl, " @deprecated").ok(),
765 };
766 }
767}
768
769fn write_requires_scopes(sdl: &mut String, requires_scopes: &[String]) {
770 write!(
771 sdl,
772 " @requiresScopes(scopes: [{}])",
773 requires_scopes
774 .iter()
775 .map(|x| {
776 "[".to_string()
777 + &x.split_whitespace()
778 .map(|y| "\"".to_string() + y + "\"")
779 .collect::<Vec<_>>()
780 .join(", ")
781 + "]"
782 })
783 .collect::<Vec<_>>()
784 .join(", ")
785 )
786 .ok();
787}
788
789fn escape_string(s: &str) -> String {
790 let mut res = String::new();
791
792 for c in s.chars() {
793 let ec = match c {
794 '\\' => Some("\\\\"),
795 '\x08' => Some("\\b"),
796 '\x0c' => Some("\\f"),
797 '\n' => Some("\\n"),
798 '\r' => Some("\\r"),
799 '\t' => Some("\\t"),
800 _ => None,
801 };
802 match ec {
803 Some(ec) => {
804 res.write_str(ec).ok();
805 }
806 None => {
807 res.write_char(c).ok();
808 }
809 }
810 }
811
812 res
813}
814
815fn tab(options: &SDLExportOptions) -> String {
816 if options.use_space_ident {
817 " ".repeat(options.indent_width.into())
818 } else {
819 "\t".to_string()
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use super::*;
826 use crate::{model::__DirectiveLocation, registry::MetaDirective};
827
828 #[test]
829 fn test_escape_string() {
830 assert_eq!(
831 escape_string("1\\\x08d\x0c3\n4\r5\t6"),
832 "1\\\\\\bd\\f3\\n4\\r5\\t6"
833 );
834 }
835
836 #[test]
837 fn test_compose_directive_dsl() {
838 let expected = r#"directive @custom_type_directive on FIELD_DEFINITION
839extend schema @link(
840 url: "https://specs.apollo.dev/federation/v2.5",
841 import: ["@key", "@tag", "@shareable", "@inaccessible", "@override", "@external", "@provides", "@requires", "@composeDirective", "@interfaceObject", "@requiresScopes"]
842)
843
844extend schema @link(
845 url: "https://custom.spec.dev/extension/v1.0"
846 import: ["@custom_type_directive"]
847)
848 @composeDirective(name: "@custom_type_directive")
849
850"#;
851 let mut registry = Registry::default();
852 registry.add_directive(MetaDirective {
853 name: "custom_type_directive".to_string(),
854 description: None,
855 locations: vec![__DirectiveLocation::FIELD_DEFINITION],
856 args: Default::default(),
857 is_repeatable: false,
858 visible: None,
859 composable: Some("https://custom.spec.dev/extension/v1.0".to_string()),
860 });
861 let dsl = registry.export_sdl(SDLExportOptions::new().federation().compose_directive());
862 assert_eq!(dsl, expected)
863 }
864
865 #[test]
866 fn test_type_directive_sdl_without_federation() {
867 let expected = r#"directive @custom_type_directive(optionalWithoutDefault: String, optionalWithDefault: String = "DEFAULT") on FIELD_DEFINITION | OBJECT
868schema {
869 query: Query
870}
871"#;
872 let mut registry = Registry::default();
873 registry.add_directive(MetaDirective {
874 name: "custom_type_directive".to_string(),
875 description: None,
876 locations: vec![
877 __DirectiveLocation::FIELD_DEFINITION,
878 __DirectiveLocation::OBJECT,
879 ],
880 args: [
881 (
882 "optionalWithoutDefault".to_string(),
883 MetaInputValue {
884 name: "optionalWithoutDefault".to_string(),
885 description: None,
886 ty: "String".to_string(),
887 deprecation: Deprecation::NoDeprecated,
888 default_value: None,
889 visible: None,
890 inaccessible: false,
891 tags: vec![],
892 is_secret: false,
893 directive_invocations: vec![],
894 },
895 ),
896 (
897 "optionalWithDefault".to_string(),
898 MetaInputValue {
899 name: "optionalWithDefault".to_string(),
900 description: None,
901 ty: "String".to_string(),
902 deprecation: Deprecation::NoDeprecated,
903 default_value: Some("\"DEFAULT\"".to_string()),
904 visible: None,
905 inaccessible: false,
906 tags: vec![],
907 is_secret: false,
908 directive_invocations: vec![],
909 },
910 ),
911 ]
912 .into(),
913 is_repeatable: false,
914 visible: None,
915 composable: None,
916 });
917 registry.query_type = "Query".to_string();
918 let sdl = registry.export_sdl(SDLExportOptions::new());
919 assert_eq!(sdl, expected)
920 }
921}