shape/
display.rs

1use super::helpers::quote_string;
2use super::NamedShapePathKey;
3use super::Shape;
4use super::ShapeCase;
5use crate::case_enum::Error;
6use std::fmt::{Display, Write as _};
7
8impl Shape {
9    /// Returns a string representation of the [`Shape`].
10    ///
11    /// Please note: this display format does not imply an input syntax or
12    /// parser for the shape language. To create new [`Shape`] elements, use the
13    /// various `Shape::*` helper functions.
14    #[must_use]
15    pub fn pretty_print(&self) -> String {
16        self.case.pretty_print()
17    }
18}
19
20impl ShapeCase {
21    #[must_use]
22    #[allow(clippy::too_many_lines)]
23    pub fn pretty_print(&self) -> String {
24        match self {
25            Self::Bool(Some(b)) => b.to_string(),
26            Self::Bool(None) => "Bool".to_string(),
27            Self::String(Some(s)) => quote_string(s.as_str()),
28            Self::String(None) => "String".to_string(),
29            Self::Int(Some(i)) => i.to_string(),
30            Self::Int(None) => "Int".to_string(),
31            Self::Float => "Float".to_string(),
32            Self::Null => "null".to_string(), // No typo: JSON null is lowercase.
33
34            Self::Unknown => "Unknown".to_string(),
35
36            // There may be some argument for using lower-case "none" here,
37            // since None is not a reserved/built-in GraphQL type name like
38            // Bool, String, Int, and Float are, so someone could define a
39            // custom GraphQL None type that would collide with this Shape name.
40            Self::None => "None".to_string(),
41
42            Self::Array { prefix, tail } => {
43                if prefix.is_empty() {
44                    if tail.is_none() {
45                        "[]".to_string()
46                    } else {
47                        format!("List<{}>", tail.pretty_print())
48                    }
49                } else {
50                    let mut result = "[".to_string();
51                    for (i, shape) in prefix.iter().enumerate() {
52                        if i > 0 {
53                            result.push_str(", ");
54                        }
55                        result.push_str(&shape.pretty_print());
56                    }
57                    if !tail.is_none() {
58                        let _ = write!(result, ", ...List<{}>", tail.pretty_print());
59                    }
60                    result.push(']');
61                    result
62                }
63            }
64
65            Self::Object { fields, rest } => {
66                if fields.is_empty() && !rest.is_none() {
67                    format!("Dict<{}>", rest.pretty_print())
68                } else {
69                    let mut result = "{".to_string();
70
71                    if !fields.is_empty() {
72                        result.push(' ');
73
74                        let mut sorted_field_names = fields.keys().collect::<Vec<_>>();
75                        sorted_field_names.sort();
76                        for (i, field_name) in sorted_field_names.into_iter().enumerate() {
77                            if i > 0 {
78                                result.push_str(", ");
79                            }
80                            let _ = write!(
81                                result,
82                                "{}: {}",
83                                field_name,
84                                fields[field_name].pretty_print()
85                            );
86                        }
87                    }
88
89                    if !rest.is_none() {
90                        if !fields.is_empty() {
91                            result.push(',');
92                        }
93                        result.push_str(format!(" ...Dict<{}>", rest.pretty_print()).as_str());
94                    }
95
96                    if result.starts_with("{ ") {
97                        result.push_str(" }");
98                    } else {
99                        result.push('}');
100                    }
101
102                    result
103                }
104            }
105
106            Self::One(shapes) => {
107                let mut result = "One<".to_string();
108                for (i, shape) in shapes.iter().enumerate() {
109                    if i > 0 {
110                        result.push_str(", ");
111                    }
112                    result.push_str(shape.pretty_print().as_str());
113                }
114                result.push('>');
115
116                // The empty `One<>` union represents an unsatisfiable shape
117                // like TypeScript's `never` type, so that's how we print it.
118                if result == "One<>" {
119                    "Never".to_string()
120                } else {
121                    result
122                }
123            }
124
125            Self::All(shapes) => {
126                let mut result = "All<".to_string();
127                for (i, shape) in shapes.iter().enumerate() {
128                    if i > 0 {
129                        result.push_str(", ");
130                    }
131                    result.push_str(shape.pretty_print().as_str());
132                }
133                result.push('>');
134                result
135            }
136
137            Self::Name(name, path) => {
138                let mut dotted_path = name.value.clone();
139                dotted_path.push_str(NamedShapePathKey::path_to_string(path).as_str());
140                dotted_path
141            }
142
143            Self::Error(Error {
144                message, partial, ..
145            }) => {
146                let mut result = format!("Error<{}", quote_string(message.as_str()));
147                if let Some(partial) = partial {
148                    let _ = write!(result, ", {}", partial.pretty_print());
149                }
150                result.push('>');
151                result
152            }
153        }
154    }
155}
156
157impl Display for Shape {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        write!(f, "{}", self.pretty_print())
160    }
161}
162
163impl Display for ShapeCase {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        write!(f, "{}", self.pretty_print())
166    }
167}