bluejay_printer/definition/
schema_definition.rs

1use crate::{
2    definition::{
3        directive_definition::DirectiveDefinitionPrinter,
4        enum_type_definition::EnumTypeDefinitionPrinter,
5        input_object_type_definition::InputObjectTypeDefinitionPrinter,
6        interface_type_definition::InterfaceTypeDefinitionPrinter,
7        object_type_definition::ObjectTypeDefinitionPrinter,
8        scalar_type_definition::ScalarTypeDefinitionPrinter,
9        union_type_definition::UnionTypeDefinitionPrinter,
10    },
11    directive::DirectivesPrinter,
12    string_value::BlockStringValuePrinter,
13};
14use bluejay_core::{
15    definition::{
16        DirectiveDefinition, ObjectTypeDefinition, SchemaDefinition, TypeDefinitionReference,
17    },
18    AsIter,
19};
20use std::fmt::{Display, Formatter, Result};
21
22pub struct SchemaDefinitionPrinter<'a, S: SchemaDefinition>(&'a S);
23
24impl<'a, S: SchemaDefinition> SchemaDefinitionPrinter<'a, S> {
25    pub fn new(schema_definition: &'a S) -> Self {
26        Self(schema_definition)
27    }
28
29    pub fn to_string(schema_definition: &'a S) -> String {
30        Self::new(schema_definition).to_string()
31    }
32
33    fn is_implicit(schema_definition: &S) -> bool {
34        schema_definition.description().is_none()
35            && schema_definition.query().name() == "Query"
36            && schema_definition
37                .mutation()
38                .map(|mutation| mutation.name() == "Mutation")
39                .unwrap_or(true)
40            && schema_definition
41                .subscription()
42                .map(|subscription| subscription.name() == "Subscription")
43                .unwrap_or(true)
44            && schema_definition
45                .directives()
46                .map(AsIter::is_empty)
47                .unwrap_or(true)
48    }
49
50    fn fmt_explicit_schema_definition(schema_definition: &S, f: &mut Formatter<'_>) -> Result {
51        if let Some(description) = schema_definition.description() {
52            write!(f, "{}", BlockStringValuePrinter::new(description, 0))?;
53        }
54
55        write!(f, "schema")?;
56
57        if let Some(directives) = schema_definition.directives() {
58            write!(f, "{}", DirectivesPrinter::new(directives))?;
59        }
60
61        writeln!(f, " {{\n  query: {}", schema_definition.query().name())?;
62
63        if let Some(mutation) = schema_definition.mutation() {
64            writeln!(f, "  mutation: {}", mutation.name())?;
65        }
66
67        if let Some(subscription) = schema_definition.subscription() {
68            writeln!(f, "  subscription: {}", subscription.name())?;
69        }
70
71        writeln!(f, "}}")
72    }
73}
74
75impl<S: SchemaDefinition> Display for SchemaDefinitionPrinter<'_, S> {
76    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
77        let Self(schema_definition) = *self;
78        schema_definition
79            .directive_definitions()
80            .filter(|dd| !dd.is_builtin())
81            .enumerate()
82            .try_for_each(|(idx, dd)| {
83                if idx != 0 {
84                    writeln!(f)?;
85                }
86                write!(f, "{}", DirectiveDefinitionPrinter::new(dd))
87            })?;
88
89        let had_directives_to_output = schema_definition
90            .directive_definitions()
91            .any(|dd| !dd.is_builtin());
92
93        schema_definition
94            .type_definitions()
95            .filter(|tdr| !tdr.is_builtin())
96            .enumerate()
97            .try_for_each(|(idx, tdr)| {
98                if had_directives_to_output || idx != 0 {
99                    writeln!(f)?;
100                }
101                match tdr {
102                    TypeDefinitionReference::BuiltinScalar(_) => Ok(()),
103                    TypeDefinitionReference::CustomScalar(cstd) => {
104                        write!(f, "{}", ScalarTypeDefinitionPrinter::new(cstd))
105                    }
106                    TypeDefinitionReference::Enum(etd) => {
107                        write!(f, "{}", EnumTypeDefinitionPrinter::new(etd))
108                    }
109                    TypeDefinitionReference::InputObject(iotd) => {
110                        write!(f, "{}", InputObjectTypeDefinitionPrinter::new(iotd))
111                    }
112                    TypeDefinitionReference::Interface(itd) => {
113                        write!(f, "{}", InterfaceTypeDefinitionPrinter::new(itd))
114                    }
115                    TypeDefinitionReference::Object(otd) => {
116                        write!(f, "{}", ObjectTypeDefinitionPrinter::new(otd))
117                    }
118                    TypeDefinitionReference::Union(utd) => {
119                        write!(f, "{}", UnionTypeDefinitionPrinter::new(utd))
120                    }
121                }
122            })?;
123
124        if Self::is_implicit(schema_definition) {
125            Ok(())
126        } else {
127            if had_directives_to_output
128                || schema_definition
129                    .type_definitions()
130                    .any(|td| !td.is_builtin())
131            {
132                writeln!(f)?;
133            }
134            Self::fmt_explicit_schema_definition(schema_definition, f)
135        }
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::SchemaDefinitionPrinter;
142    use bluejay_parser::ast::{
143        definition::{DefinitionDocument, SchemaDefinition},
144        Parse,
145    };
146
147    #[test]
148    fn test_schema_dump() {
149        insta::glob!("test_data/schema_definition/*.graphql", |path| {
150            let input = std::fs::read_to_string(path).unwrap();
151            let document: DefinitionDocument = DefinitionDocument::parse(input.as_str()).unwrap();
152            let schema_definition = SchemaDefinition::try_from(&document).unwrap();
153            similar_asserts::assert_eq!(
154                input,
155                SchemaDefinitionPrinter(&schema_definition).to_string()
156            );
157        });
158    }
159}