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