graphql_composition/
lib.rs

1#![deny(unsafe_code, missing_docs, rust_2018_idioms)]
2#![doc = include_str!("../README.md")]
3
4pub mod diagnostics;
5
6mod compose;
7mod composition_ir;
8mod emit_federated_graph;
9mod federated_graph;
10mod grafbase_extensions;
11mod ingest_subgraph;
12mod result;
13mod subgraphs;
14mod validate;
15
16pub use self::{
17    diagnostics::Diagnostics,
18    federated_graph::{DomainError, FederatedGraph, render_api_sdl, render_federated_sdl},
19    grafbase_extensions::LoadedExtension,
20    result::CompositionResult,
21    subgraphs::{IngestError, Subgraphs},
22};
23
24use self::{
25    compose::{ComposeContext, compose_subgraphs},
26    emit_federated_graph::emit_federated_graph,
27    ingest_subgraph::ast_value_to_subgraph_value,
28};
29
30/// Compose subgraphs into a federated graph.
31pub fn compose(subgraphs: &mut Subgraphs) -> CompositionResult {
32    subgraphs.sort_pre_composition();
33
34    let mut diagnostics = Diagnostics::default();
35
36    if subgraphs.iter_subgraphs().len() == 0 {
37        let error = "No subgraphs to compose. You must pass at least one subgraph.";
38        diagnostics.push_fatal(error.to_owned());
39
40        return CompositionResult {
41            federated_graph: None,
42            diagnostics,
43        };
44    }
45
46    let mut context = ComposeContext::new(subgraphs, &mut diagnostics);
47
48    validate::validate_pre_merge(&mut context);
49
50    if context.diagnostics.any_fatal() {
51        return CompositionResult {
52            federated_graph: None,
53            diagnostics,
54        };
55    }
56
57    for (_, directive) in subgraphs.iter_extra_directives_on_schema_definition() {
58        let subgraphs::DirectiveProvenance::Linked {
59            linked_schema_id,
60            is_composed_directive,
61        } = directive.provenance
62        else {
63            continue;
64        };
65
66        if let Some(extension_id) = context.get_extension_for_linked_schema(linked_schema_id) {
67            context.mark_used_extension(extension_id);
68        } else if !is_composed_directive {
69            context.diagnostics.push_warning(format!(
70                "Directive `{}` is not defined in any extension or composed directive",
71                &context[directive.name]
72            ));
73        }
74    }
75    compose_subgraphs(&mut context);
76
77    if context.diagnostics.any_fatal() {
78        CompositionResult {
79            federated_graph: None,
80            diagnostics,
81        }
82    } else {
83        let federated_graph = emit_federated_graph(context.into_ir(), subgraphs);
84
85        CompositionResult {
86            federated_graph: Some(federated_graph),
87            diagnostics,
88        }
89    }
90}
91
92trait VecExt<T> {
93    fn push_return_idx(&mut self, elem: T) -> usize;
94}
95
96impl<T> VecExt<T> for Vec<T> {
97    fn push_return_idx(&mut self, elem: T) -> usize {
98        let idx = self.len();
99        self.push(elem);
100        idx
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn grafbase_schema_can_be_composed() {
110        use std::{fs, path::Path};
111        let schema_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cli/src/api/graphql/api.graphql");
112        let schema = fs::read_to_string(schema_path).unwrap();
113
114        let mut subgraphs = Subgraphs::default();
115        subgraphs
116            .ingest_str(&schema, "grafbase-api", Some("https://api.grafbase.com"))
117            .unwrap();
118        let result = compose(&mut subgraphs);
119        assert!(!result.diagnostics().any_fatal());
120    }
121}