graphql_composition/
lib.rs

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