graphql_federated_graph/render_sdl/
render_api_sdl.rs1use super::{directive::write_directive, directive_definition::display_directive_definitions, display_utils::*};
2use crate::{FederatedGraph, directives::*, federated_graph::*};
3use std::fmt::{self, Write as _};
4
5pub fn render_api_sdl(graph: &FederatedGraph) -> String {
9 Renderer { graph }.to_string()
10}
11
12struct Renderer<'a> {
13 graph: &'a FederatedGraph,
14}
15
16impl fmt::Display for Renderer<'_> {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 let Renderer { graph } = self;
19
20 let mut write_leading_whitespace = {
22 let mut first_block = true;
23 move |f: &mut fmt::Formatter<'_>| {
24 if first_block {
25 first_block = false;
26 Ok(())
27 } else {
28 f.write_char('\n')
29 }
30 }
31 };
32
33 display_directive_definitions(|def| def.namespace.is_none(), public_directives_filter, graph, f)?;
34
35 for r#enum in graph.iter_enum_definitions() {
36 if has_inaccessible(&r#enum.directives) || r#enum.namespace.is_some() {
37 continue;
38 }
39
40 write_leading_whitespace(f)?;
41
42 write_description(f, r#enum.description, "", graph)?;
43 f.write_str("enum ")?;
44 f.write_str(&graph[r#enum.name])?;
45 write_public_directives(f, &r#enum.directives, graph)?;
46 f.write_char(' ')?;
47
48 write_block(f, |f| {
49 for variant in graph.iter_enum_values(r#enum.id()) {
50 if has_inaccessible(&variant.directives) {
51 continue;
52 }
53
54 write_enum_variant(f, &variant, graph)?;
55 }
56
57 Ok(())
58 })?;
59
60 f.write_char('\n')?;
61 }
62
63 for object in graph.iter_objects() {
64 if has_inaccessible(&object.directives) {
65 continue;
66 }
67
68 if graph[object.fields.clone()].iter().all(|field| {
69 let field_name = &graph[field.name];
70 field_name.starts_with("__") || has_inaccessible(&field.directives)
71 }) {
72 continue;
73 }
74
75 write_leading_whitespace(f)?;
76
77 write_description(f, object.description, "", graph)?;
78 f.write_str("type ")?;
79 f.write_str(&graph[object.name])?;
80 write_public_directives(f, &object.directives, graph)?;
81 f.write_char(' ')?;
82
83 write_block(f, |f| {
84 for field in &graph[object.fields.clone()] {
85 let field_name = &graph[field.name];
86
87 if field_name.starts_with("__") || has_inaccessible(&field.directives) {
88 continue;
89 }
90
91 write_description(f, field.description, INDENT, graph)?;
92 f.write_str(INDENT)?;
93 f.write_str(field_name)?;
94 write_field_arguments(f, &graph[field.arguments], graph)?;
95 f.write_str(": ")?;
96 f.write_str(&render_field_type(&field.r#type, graph))?;
97 write_public_directives(f, &field.directives, graph)?;
98 f.write_char('\n')?;
99 }
100
101 Ok(())
102 })?;
103
104 f.write_char('\n')?;
105 }
106
107 for interface in &graph.interfaces {
108 if has_inaccessible(&interface.directives) {
109 continue;
110 }
111
112 write_leading_whitespace(f)?;
113
114 write_description(f, interface.description, "", graph)?;
115 f.write_str("interface ")?;
116 f.write_str(&graph[interface.name])?;
117 write_public_directives(f, &interface.directives, graph)?;
118 f.write_char(' ')?;
119
120 write_block(f, |f| {
121 for field in &graph[interface.fields.clone()] {
122 if has_inaccessible(&field.directives) {
123 continue;
124 }
125
126 let field_name = &graph[field.name];
127 write_description(f, field.description, INDENT, graph)?;
128 f.write_str(INDENT)?;
129 f.write_str(field_name)?;
130 write_field_arguments(f, &graph[field.arguments], graph)?;
131 f.write_str(": ")?;
132 f.write_str(&render_field_type(&field.r#type, graph))?;
133 write_public_directives(f, &field.directives, graph)?;
134 f.write_char('\n')?;
135 }
136
137 Ok(())
138 })?;
139
140 f.write_char('\n')?;
141 }
142
143 for input_object in &graph.input_objects {
144 if has_inaccessible(&input_object.directives) {
145 continue;
146 }
147
148 write_leading_whitespace(f)?;
149
150 write_description(f, input_object.description, "", graph)?;
151 f.write_str("input ")?;
152 f.write_str(&graph[input_object.name])?;
153 write_public_directives(f, &input_object.directives, graph)?;
154
155 f.write_char(' ')?;
156
157 write_block(f, |f| {
158 for field in &graph[input_object.fields] {
159 if has_inaccessible(&field.directives) {
160 continue;
161 }
162
163 write_description(f, field.description, INDENT, graph)?;
164 let field_name = &graph[field.name];
165 f.write_str(INDENT)?;
166 f.write_str(field_name)?;
167 f.write_str(": ")?;
168 f.write_str(&render_field_type(&field.r#type, graph))?;
169
170 if let Some(default) = &field.default {
171 write!(f, " = {}", ValueDisplay(default, graph))?;
172 }
173
174 write_public_directives(f, &field.directives, graph)?;
175 f.write_char('\n')?;
176 }
177
178 Ok(())
179 })?;
180
181 f.write_char('\n')?;
182 }
183
184 for union in &graph.unions {
185 if has_inaccessible(&union.directives) {
186 continue;
187 }
188
189 write_leading_whitespace(f)?;
190
191 write_description(f, union.description, "", graph)?;
192 f.write_str("union ")?;
193 f.write_str(&graph[union.name])?;
194 write_public_directives(f, &union.directives, graph)?;
195 f.write_str(" =")?;
196
197 let mut members = union.members.iter().peekable();
198
199 while let Some(member) = members.next() {
200 f.write_str(" ")?;
201 f.write_str(graph.at(*member).then(|obj| obj.name).as_str())?;
202
203 if members.peek().is_some() {
204 f.write_str(" |")?;
205 }
206 }
207
208 f.write_char('\n')?;
209 }
210
211 for scalar in graph.iter_scalar_definitions() {
212 let scalar_name = scalar.then(|scalar| scalar.name).as_str();
213
214 if scalar.namespace.is_some() {
215 continue;
216 }
217
218 if BUILTIN_SCALARS.contains(&scalar_name) || has_inaccessible(&scalar.directives) {
219 continue;
220 }
221
222 write_leading_whitespace(f)?;
223
224 write_description(f, scalar.description, "", graph)?;
225 f.write_str("scalar ")?;
226 f.write_str(scalar_name)?;
227 write_public_directives(f, &scalar.directives, graph)?;
228
229 f.write_char('\n')?;
230 }
231
232 Ok(())
233 }
234}
235
236fn has_inaccessible(directives: &[Directive]) -> bool {
237 directives
238 .iter()
239 .any(|directive| matches!(directive, Directive::Inaccessible))
240}
241
242fn public_directives_filter(directive: &Directive, graph: &FederatedGraph) -> bool {
243 match directive {
244 Directive::Inaccessible
245 | Directive::Policy(_)
246 | Directive::RequiresScopes(_)
247 | Directive::Authenticated
248 | Directive::Cost { .. }
249 | Directive::JoinField(_)
250 | Directive::JoinType(_)
251 | Directive::JoinUnionMember(_)
252 | Directive::JoinImplements(_)
253 | Directive::Authorized(_)
254 | Directive::ListSize(_)
255 | Directive::JoinGraph(_)
256 | Directive::ExtensionDirective { .. } => false,
257
258 Directive::Other { name, .. } if graph[*name] == "tag" => false,
259 Directive::Deprecated { .. } | Directive::Other { .. } => true,
260 }
261}
262
263fn write_public_directives<'a, 'b: 'a>(
264 f: &'a mut fmt::Formatter<'b>,
265 directives: &[Directive],
266 graph: &'a FederatedGraph,
267) -> fmt::Result {
268 for directive in directives
269 .iter()
270 .filter(|directive| public_directives_filter(directive, graph))
271 {
272 f.write_str(" ")?;
273 write_directive(f, directive, graph)?;
274 }
275
276 Ok(())
277}
278
279fn write_enum_variant<'a, 'b: 'a>(
280 f: &'a mut fmt::Formatter<'b>,
281 enum_variant: &EnumValueRecord,
282 graph: &'a FederatedGraph,
283) -> fmt::Result {
284 f.write_str(INDENT)?;
285 write_description(f, enum_variant.description, INDENT, graph)?;
286 f.write_str(&graph[enum_variant.value])?;
287 write_public_directives(f, &enum_variant.directives, graph)?;
288 f.write_char('\n')
289}
290
291fn write_field_arguments<'a, 'b: 'a>(
292 f: &'a mut fmt::Formatter<'b>,
293 args: &[InputValueDefinition],
294 graph: &'a FederatedGraph,
295) -> fmt::Result {
296 if args.is_empty() {
297 return Ok(());
298 }
299
300 let mut inner = args
301 .iter()
302 .map(|arg| {
303 let name = &graph[arg.name];
304 let r#type = render_field_type(&arg.r#type, graph);
305 let directives = &arg.directives;
306 let default = arg.default.as_ref();
307 let description = arg.description;
308 (name, r#type, directives, default, description)
309 })
310 .peekable();
311
312 f.write_str("(")?;
313
314 while let Some((name, ty, directives, default, description)) = inner.next() {
315 if let Some(description) = description {
316 display_graphql_string_literal(&graph[description], f)?;
317 f.write_str(" ")?;
318 }
319
320 f.write_str(name)?;
321 f.write_str(": ")?;
322 f.write_str(&ty)?;
323
324 if let Some(default) = default {
325 write!(f, " = {}", ValueDisplay(default, graph))?;
326 }
327
328 write_public_directives(f, directives, graph)?;
329
330 if inner.peek().is_some() {
331 f.write_str(", ")?;
332 }
333 }
334
335 f.write_str(")")
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_empty() {
344 let empty = FederatedGraph::default();
345 let sdl = render_api_sdl(&empty);
346 assert!(sdl.is_empty());
347 }
348}