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: Subgraphs) -> CompositionResult {
32    let subgraphs = subgraphs.finalize();
33    let mut diagnostics = Diagnostics::default();
34
35    if subgraphs.iter_subgraphs().len() == 0 {
36        let error = "No graphs found for composition build. You must have at least one active graph.";
37        diagnostics.push_fatal(error.to_owned());
38
39        return CompositionResult {
40            federated_graph: None,
41            diagnostics,
42        };
43    }
44
45    let mut context = ComposeContext::new(&subgraphs, &mut diagnostics);
46
47    validate::validate(&mut context);
48
49    if context.diagnostics.any_fatal() {
50        return CompositionResult {
51            federated_graph: None,
52            diagnostics,
53        };
54    }
55
56    for (_, directive) in subgraphs.iter_extra_directives_on_schema_definition() {
57        let subgraphs::DirectiveProvenance::Linked {
58            linked_schema_id,
59            is_composed_directive,
60        } = directive.provenance
61        else {
62            continue;
63        };
64
65        if let Some(extension_id) = context.get_extension_for_linked_schema(linked_schema_id) {
66            context.mark_used_extension(extension_id);
67        } else if !is_composed_directive {
68            context.diagnostics.push_warning(format!(
69                "Directive `{}` is not defined in any extension or composed directive",
70                &context[directive.name]
71            ));
72        }
73    }
74    compose_subgraphs(&mut context);
75
76    if context.diagnostics.any_fatal() {
77        CompositionResult {
78            federated_graph: None,
79            diagnostics,
80        }
81    } else {
82        let federated_graph = emit_federated_graph(context.into_ir(), &subgraphs);
83
84        CompositionResult {
85            federated_graph: Some(federated_graph),
86            diagnostics,
87        }
88    }
89}
90
91trait VecExt<T> {
92    fn push_return_idx(&mut self, elem: T) -> usize;
93}
94
95impl<T> VecExt<T> for Vec<T> {
96    fn push_return_idx(&mut self, elem: T) -> usize {
97        let idx = self.len();
98        self.push(elem);
99        idx
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn grafbase_schema_can_be_composed() {
109        use std::{fs, path::Path};
110        let schema_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cli/src/api/graphql/api.graphql");
111        let schema = fs::read_to_string(schema_path).unwrap();
112
113        let mut subgraphs = Subgraphs::default();
114        subgraphs
115            .ingest_str(&schema, "grafbase-api", Some("https://api.grafbase.com"))
116            .unwrap();
117        let result = compose(subgraphs);
118        assert!(!result.diagnostics().any_fatal());
119    }
120}