graphql_composition/
federated_graph.rs

1//! A structured representation of a federated GraphQL schema. Can be instantiated by [composition](https://crates.io/crates/graphql-composition) or [from SDL](`from_sdl()`).
2
3#![expect(missing_docs)]
4
5mod debug;
6mod directive_definitions;
7mod directives;
8mod entity;
9mod enum_definitions;
10mod enum_values;
11mod extensions;
12mod from_sdl;
13mod ids;
14mod input_value_definitions;
15mod objects;
16mod render_sdl;
17mod roots;
18mod scalar_definitions;
19mod r#type;
20mod view;
21
22pub use self::{
23    from_sdl::DomainError,
24    render_sdl::{render_api_sdl, render_federated_sdl},
25};
26
27pub(crate) use self::{
28    directive_definitions::*,
29    directives::*,
30    entity::*,
31    enum_definitions::EnumDefinitionRecord,
32    enum_values::EnumValueRecord,
33    extensions::*,
34    ids::*,
35    render_sdl::display_graphql_string_literal,
36    roots::*,
37    scalar_definitions::ScalarDefinitionRecord,
38    r#type::{Definition, Type},
39    view::{View, ViewNested},
40};
41
42use enum_definitions::EnumDefinition;
43use scalar_definitions::ScalarDefinition;
44use std::{fmt, ops::Range};
45
46#[derive(Default)]
47pub struct FederatedGraph {
48    pub(crate) subgraphs: Vec<Subgraph>,
49    pub extensions: Vec<Extension>,
50    pub(crate) objects: Vec<Object>,
51    pub(crate) interfaces: Vec<Interface>,
52    pub(crate) fields: Vec<Field>,
53    pub(crate) roots: SchemaRoots,
54
55    pub(crate) directive_definitions: Vec<DirectiveDefinitionRecord>,
56    pub(crate) directive_definition_arguments: Vec<DirectiveDefinitionArgument>,
57    pub(crate) scalar_definitions: Vec<ScalarDefinitionRecord>,
58    pub(crate) enum_definitions: Vec<EnumDefinitionRecord>,
59    pub(crate) unions: Vec<Union>,
60    pub(crate) input_objects: Vec<InputObject>,
61    pub(crate) enum_values: Vec<EnumValueRecord>,
62    pub(crate) linked_schemas: Vec<LinkDirective>,
63    pub(crate) composed_directives_on_schema_definition: Vec<directives::OtherDirective>,
64
65    /// All [input value definitions](http://spec.graphql.org/October2021/#InputValueDefinition) in the federated graph. Concretely, these are arguments of output fields, and input object fields.
66    pub(crate) input_value_definitions: Vec<InputValueDefinition>,
67
68    /// All the strings in the federated graph, deduplicated.
69    pub strings: Vec<String>,
70}
71
72impl FederatedGraph {
73    pub fn from_sdl(sdl: &str) -> Result<Self, crate::DomainError> {
74        if sdl.trim().is_empty() {
75            return Ok(Default::default());
76        }
77        from_sdl::from_sdl(sdl)
78    }
79
80    pub(crate) fn definition_name(&self, definition: Definition) -> &str {
81        let name_id = match definition {
82            Definition::Scalar(scalar_id) => self[scalar_id].name,
83            Definition::Object(object_id) => self.at(object_id).name,
84            Definition::Interface(interface_id) => self.at(interface_id).name,
85            Definition::Union(union_id) => self[union_id].name,
86            Definition::Enum(enum_id) => self[enum_id].name,
87            Definition::InputObject(input_object_id) => self[input_object_id].name,
88        };
89
90        &self[name_id]
91    }
92
93    pub fn iter_interfaces(&self) -> impl ExactSizeIterator<Item = View<InterfaceId, &Interface>> {
94        (0..self.interfaces.len()).map(|idx| self.view(InterfaceId::from(idx)))
95    }
96
97    pub fn iter_objects(&self) -> impl ExactSizeIterator<Item = View<ObjectId, &Object>> {
98        (0..self.objects.len()).map(|idx| self.view(ObjectId::from(idx)))
99    }
100
101    pub fn iter_scalar_definitions(&self) -> impl Iterator<Item = ScalarDefinition<'_>> {
102        self.scalar_definitions
103            .iter()
104            .enumerate()
105            .map(|(idx, _)| self.at(ScalarDefinitionId::from(idx)))
106    }
107
108    pub fn iter_enum_definitions(&self) -> impl Iterator<Item = EnumDefinition<'_>> {
109        self.enum_definitions
110            .iter()
111            .enumerate()
112            .map(|(idx, _)| self.at(EnumDefinitionId::from(idx)))
113    }
114}
115
116#[derive(Clone, Debug)]
117pub struct Subgraph {
118    pub name: StringId,
119    pub join_graph_enum_value: EnumValueId,
120    pub url: Option<StringId>,
121}
122
123#[derive(Clone, Debug)]
124pub struct Union {
125    pub(crate) name: StringId,
126    pub(crate) description: Option<StringId>,
127    pub(crate) members: Vec<ObjectId>,
128    pub(crate) directives: Vec<Directive>,
129}
130
131#[derive(Clone, Debug)]
132pub struct InputObject {
133    pub(crate) name: StringId,
134    pub(crate) description: Option<StringId>,
135    pub(crate) fields: InputValueDefinitions,
136    pub(crate) directives: Vec<Directive>,
137}
138
139#[derive(Default, Clone, PartialEq, PartialOrd, Debug)]
140#[allow(clippy::enum_variant_names)]
141pub(crate) enum Value {
142    #[default]
143    Null,
144    String(StringId),
145    Int(i64),
146    Float(f64),
147    Boolean(bool),
148    /// Different from `String`.
149    ///
150    /// `@tag(name: "SOMETHING")` vs `@tag(name: SOMETHING)`
151    ///
152    /// FIXME: This is currently required because we do not keep accurate track of the directives in use in the schema, but we should strive towards removing UnboundEnumValue in favour of EnumValue.
153    UnboundEnumValue(StringId),
154    EnumValue(EnumValueId),
155    Object(Box<[(StringId, Value)]>),
156    List(Box<[Value]>),
157}
158
159#[derive(Clone, Debug)]
160pub struct Object {
161    pub(crate) name: StringId,
162    pub(crate) directives: Vec<Directive>,
163    pub(crate) description: Option<StringId>,
164    pub(crate) implements_interfaces: Vec<InterfaceId>,
165    pub(crate) fields: Fields,
166}
167
168#[derive(Clone, Debug)]
169pub struct Interface {
170    pub(crate) name: StringId,
171    pub(crate) directives: Vec<Directive>,
172    pub(crate) description: Option<StringId>,
173    pub(crate) implements_interfaces: Vec<InterfaceId>,
174    pub(crate) fields: Fields,
175}
176
177#[derive(Clone, Debug)]
178pub struct Field {
179    pub(crate) parent_entity_id: EntityDefinitionId,
180    pub(crate) name: StringId,
181    pub(crate) description: Option<StringId>,
182    pub(crate) r#type: Type,
183    pub(crate) arguments: InputValueDefinitions,
184    pub(crate) directives: Vec<Directive>,
185}
186
187#[derive(Clone, PartialEq, Debug)]
188pub struct InputValueDefinition {
189    pub(crate) name: StringId,
190    pub(crate) r#type: Type,
191    pub(crate) directives: Vec<Directive>,
192    pub(crate) description: Option<StringId>,
193    pub(crate) default: Option<Value>,
194}
195
196#[derive(Clone, Debug, PartialEq, PartialOrd)]
197pub(crate) struct SelectionSet(pub(crate) Vec<Selection>);
198
199impl From<Vec<Selection>> for SelectionSet {
200    fn from(selections: Vec<Selection>) -> Self {
201        SelectionSet(selections)
202    }
203}
204
205impl FromIterator<Selection> for SelectionSet {
206    fn from_iter<I: IntoIterator<Item = Selection>>(iter: I) -> Self {
207        SelectionSet(iter.into_iter().collect())
208    }
209}
210
211impl std::ops::Deref for SelectionSet {
212    type Target = Vec<Selection>;
213    fn deref(&self) -> &Self::Target {
214        &self.0
215    }
216}
217
218impl std::ops::DerefMut for SelectionSet {
219    fn deref_mut(&mut self) -> &mut Self::Target {
220        &mut self.0
221    }
222}
223
224#[derive(Clone, Debug, PartialEq, PartialOrd)]
225pub(crate) enum Selection {
226    Typename,
227    Field(FieldSelection),
228    InlineFragment { on: Definition, subselection: SelectionSet },
229}
230
231#[derive(Clone, Debug, PartialEq, PartialOrd)]
232pub struct FieldSelection {
233    pub field_id: FieldId,
234    pub arguments: Vec<(InputValueDefinitionId, Value)>,
235    pub subselection: SelectionSet,
236}
237
238impl std::ops::Index<InputValueDefinitions> for FederatedGraph {
239    type Output = [InputValueDefinition];
240
241    fn index(&self, index: InputValueDefinitions) -> &Self::Output {
242        let (start, len) = index;
243        &self.input_value_definitions[usize::from(start)..(usize::from(start) + len)]
244    }
245}
246
247impl std::ops::Index<Fields> for FederatedGraph {
248    type Output = [Field];
249
250    fn index(&self, index: Fields) -> &Self::Output {
251        &self.fields[usize::from(index.start)..usize::from(index.end)]
252    }
253}
254
255pub type InputValueDefinitionSet = Vec<InputValueDefinitionSetItem>;
256
257#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, PartialOrd)]
258pub struct InputValueDefinitionSetItem {
259    pub input_value_definition: InputValueDefinitionId,
260    pub subselection: InputValueDefinitionSet,
261}
262
263/// A (start, end) range in FederatedGraph::fields.
264pub type Fields = Range<FieldId>;
265/// A (start, len) range in FederatedSchema.
266pub type InputValueDefinitions = (InputValueDefinitionId, usize);
267
268pub const NO_INPUT_VALUE_DEFINITION: InputValueDefinitions = (InputValueDefinitionId::const_from_usize(0), 0);
269pub const NO_FIELDS: Fields = Range {
270    start: FieldId::const_from_usize(0),
271    end: FieldId::const_from_usize(0),
272};