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::OneOf
246 | Directive::Policy(_)
247 | Directive::RequiresScopes(_)
248 | Directive::Authenticated
249 | Directive::Cost { .. }
250 | Directive::JoinField(_)
251 | Directive::JoinType(_)
252 | Directive::JoinUnionMember(_)
253 | Directive::JoinImplements(_)
254 | Directive::Authorized(_)
255 | Directive::ListSize(_)
256 | Directive::JoinGraph(_)
257 | Directive::CompositeLookup { .. }
258 | Directive::CompositeDerive { .. }
259 | Directive::CompositeRequire { .. }
260 | Directive::CompositeIs { .. }
261 | Directive::ExtensionDirective { .. } => false,
262
263 Directive::Other { name, .. } if graph[*name] == "tag" => false,
264 Directive::Deprecated { .. } | Directive::Other { .. } => true,
265 }
266}
267
268fn write_public_directives<'a, 'b: 'a>(
269 f: &'a mut fmt::Formatter<'b>,
270 directives: &[Directive],
271 graph: &'a FederatedGraph,
272) -> fmt::Result {
273 for directive in directives
274 .iter()
275 .filter(|directive| public_directives_filter(directive, graph))
276 {
277 f.write_str(" ")?;
278 write_directive(f, directive, graph)?;
279 }
280
281 Ok(())
282}
283
284fn write_enum_variant<'a, 'b: 'a>(
285 f: &'a mut fmt::Formatter<'b>,
286 enum_variant: &EnumValueRecord,
287 graph: &'a FederatedGraph,
288) -> fmt::Result {
289 f.write_str(INDENT)?;
290 write_description(f, enum_variant.description, INDENT, graph)?;
291 f.write_str(&graph[enum_variant.value])?;
292 write_public_directives(f, &enum_variant.directives, graph)?;
293 f.write_char('\n')
294}
295
296fn write_field_arguments<'a, 'b: 'a>(
297 f: &'a mut fmt::Formatter<'b>,
298 args: &[InputValueDefinition],
299 graph: &'a FederatedGraph,
300) -> fmt::Result {
301 if args.is_empty() {
302 return Ok(());
303 }
304
305 let mut inner = args
306 .iter()
307 .map(|arg| {
308 let name = &graph[arg.name];
309 let r#type = render_field_type(&arg.r#type, graph);
310 let directives = &arg.directives;
311 let default = arg.default.as_ref();
312 let description = arg.description;
313 (name, r#type, directives, default, description)
314 })
315 .peekable();
316
317 f.write_str("(")?;
318
319 while let Some((name, ty, directives, default, description)) = inner.next() {
320 if let Some(description) = description {
321 display_graphql_string_literal(&graph[description], f)?;
322 f.write_str(" ")?;
323 }
324
325 f.write_str(name)?;
326 f.write_str(": ")?;
327 f.write_str(&ty)?;
328
329 if let Some(default) = default {
330 write!(f, " = {}", ValueDisplay(default, graph))?;
331 }
332
333 write_public_directives(f, directives, graph)?;
334
335 if inner.peek().is_some() {
336 f.write_str(", ")?;
337 }
338 }
339
340 f.write_str(")")
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn test_empty() {
349 let empty = FederatedGraph::default();
350 let sdl = render_api_sdl(&empty);
351 assert!(sdl.is_empty());
352 }
353}