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 scalar_definitions;
18mod r#type;
19mod view;
20
21pub use self::{
22    from_sdl::DomainError,
23    render_sdl::{render_api_sdl, render_federated_sdl},
24};
25
26pub(crate) use self::{
27    directive_definitions::*,
28    directives::*,
29    entity::*,
30    enum_definitions::EnumDefinitionRecord,
31    enum_values::EnumValueRecord,
32    extensions::*,
33    ids::*,
34    render_sdl::display_graphql_string_literal,
35    scalar_definitions::ScalarDefinitionRecord,
36    r#type::{Definition, Type},
37    view::{View, ViewNested},
38};
39
40use enum_definitions::EnumDefinition;
41use scalar_definitions::ScalarDefinition;
42use std::{fmt, ops::Range};
43
44#[derive(Clone, Default)]
45pub struct FederatedGraph {
46    pub(crate) subgraphs: Vec<Subgraph>,
47    pub extensions: Vec<Extension>,
48    pub(crate) objects: Vec<Object>,
49    pub(crate) interfaces: Vec<Interface>,
50    pub(crate) fields: Vec<Field>,
51
52    pub(crate) directive_definitions: Vec<DirectiveDefinitionRecord>,
53    pub(crate) directive_definition_arguments: Vec<DirectiveDefinitionArgument>,
54    pub(crate) scalar_definitions: Vec<ScalarDefinitionRecord>,
55    pub(crate) enum_definitions: Vec<EnumDefinitionRecord>,
56    pub(crate) unions: Vec<Union>,
57    pub(crate) input_objects: Vec<InputObject>,
58    pub(crate) enum_values: Vec<EnumValueRecord>,
59
60    /// 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.
61    pub(crate) input_value_definitions: Vec<InputValueDefinition>,
62
63    /// All the strings in the federated graph, deduplicated.
64    pub strings: Vec<String>,
65}
66
67impl FederatedGraph {
68    pub fn from_sdl(sdl: &str) -> Result<Self, crate::DomainError> {
69        if sdl.trim().is_empty() {
70            return Ok(Default::default());
71        }
72        from_sdl::from_sdl(sdl)
73    }
74
75    pub fn definition_name(&self, definition: Definition) -> &str {
76        let name_id = match definition {
77            Definition::Scalar(scalar_id) => self[scalar_id].name,
78            Definition::Object(object_id) => self.at(object_id).name,
79            Definition::Interface(interface_id) => self.at(interface_id).name,
80            Definition::Union(union_id) => self[union_id].name,
81            Definition::Enum(enum_id) => self[enum_id].name,
82            Definition::InputObject(input_object_id) => self[input_object_id].name,
83        };
84
85        &self[name_id]
86    }
87
88    pub fn iter_interfaces(&self) -> impl ExactSizeIterator<Item = View<InterfaceId, &Interface>> {
89        (0..self.interfaces.len()).map(|idx| self.view(InterfaceId::from(idx)))
90    }
91
92    pub fn iter_objects(&self) -> impl ExactSizeIterator<Item = View<ObjectId, &Object>> {
93        (0..self.objects.len()).map(|idx| self.view(ObjectId::from(idx)))
94    }
95
96    pub fn iter_scalar_definitions(&self) -> impl Iterator<Item = ScalarDefinition<'_>> {
97        self.scalar_definitions
98            .iter()
99            .enumerate()
100            .map(|(idx, _)| self.at(ScalarDefinitionId::from(idx)))
101    }
102
103    pub fn iter_enum_definitions(&self) -> impl Iterator<Item = EnumDefinition<'_>> {
104        self.enum_definitions
105            .iter()
106            .enumerate()
107            .map(|(idx, _)| self.at(EnumDefinitionId::from(idx)))
108    }
109}
110
111#[derive(Clone, Debug)]
112pub struct Subgraph {
113    pub name: StringId,
114    pub join_graph_enum_value: EnumValueId,
115    pub url: Option<StringId>,
116}
117
118#[derive(Clone, Debug)]
119pub struct Union {
120    pub name: StringId,
121    pub description: Option<StringId>,
122    pub members: Vec<ObjectId>,
123    pub directives: Vec<Directive>,
124}
125
126#[derive(Clone, Debug)]
127pub struct InputObject {
128    pub name: StringId,
129    pub description: Option<StringId>,
130    pub fields: InputValueDefinitions,
131    pub directives: Vec<Directive>,
132}
133
134#[derive(Default, Clone, PartialEq, PartialOrd, Debug)]
135#[allow(clippy::enum_variant_names)]
136pub enum Value {
137    #[default]
138    Null,
139    String(StringId),
140    Int(i64),
141    Float(f64),
142    Boolean(bool),
143    /// Different from `String`.
144    ///
145    /// `@tag(name: "SOMETHING")` vs `@tag(name: SOMETHING)`
146    ///
147    /// 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.
148    UnboundEnumValue(StringId),
149    EnumValue(EnumValueId),
150    Object(Box<[(StringId, Value)]>),
151    List(Box<[Value]>),
152}
153
154#[derive(Clone, Debug)]
155pub struct Object {
156    pub name: StringId,
157    pub directives: Vec<Directive>,
158    pub description: Option<StringId>,
159    pub implements_interfaces: Vec<InterfaceId>,
160    pub fields: Fields,
161}
162
163#[derive(Clone, Debug)]
164pub struct Interface {
165    pub name: StringId,
166    pub directives: Vec<Directive>,
167    pub description: Option<StringId>,
168    pub implements_interfaces: Vec<InterfaceId>,
169    pub fields: Fields,
170}
171
172#[derive(Clone, Debug)]
173pub struct Field {
174    pub parent_entity_id: EntityDefinitionId,
175    pub name: StringId,
176    pub description: Option<StringId>,
177    pub r#type: Type,
178    pub arguments: InputValueDefinitions,
179    pub directives: Vec<Directive>,
180}
181
182impl Value {
183    pub fn is_list(&self) -> bool {
184        matches!(self, Value::List(_))
185    }
186
187    pub fn is_null(&self) -> bool {
188        matches!(self, Value::Null)
189    }
190}
191
192#[derive(Clone, PartialEq, Debug)]
193pub struct InputValueDefinition {
194    pub name: StringId,
195    pub r#type: Type,
196    pub directives: Vec<Directive>,
197    pub description: Option<StringId>,
198    pub default: Option<Value>,
199}
200
201#[derive(Clone, Debug, PartialEq, PartialOrd)]
202pub struct SelectionSet(pub Vec<Selection>);
203
204impl From<Vec<Selection>> for SelectionSet {
205    fn from(selections: Vec<Selection>) -> Self {
206        SelectionSet(selections)
207    }
208}
209
210impl FromIterator<Selection> for SelectionSet {
211    fn from_iter<I: IntoIterator<Item = Selection>>(iter: I) -> Self {
212        SelectionSet(iter.into_iter().collect())
213    }
214}
215
216impl std::ops::Deref for SelectionSet {
217    type Target = Vec<Selection>;
218    fn deref(&self) -> &Self::Target {
219        &self.0
220    }
221}
222
223impl std::ops::DerefMut for SelectionSet {
224    fn deref_mut(&mut self) -> &mut Self::Target {
225        &mut self.0
226    }
227}
228
229impl SelectionSet {
230    pub fn find_field(&self, field_id: FieldId) -> Option<&FieldSelection> {
231        for selection in &self.0 {
232            match selection {
233                Selection::Field(field) => {
234                    if field.field_id == field_id {
235                        return Some(field);
236                    }
237                }
238                Selection::InlineFragment { subselection, .. } => {
239                    if let Some(found) = subselection.find_field(field_id) {
240                        return Some(found);
241                    }
242                }
243            }
244        }
245        None
246    }
247}
248
249#[derive(Clone, Debug, PartialEq, PartialOrd)]
250pub enum Selection {
251    Field(FieldSelection),
252    InlineFragment { on: Definition, subselection: SelectionSet },
253}
254
255#[derive(Clone, Debug, PartialEq, PartialOrd)]
256pub struct FieldSelection {
257    pub field_id: FieldId,
258    pub arguments: Vec<(InputValueDefinitionId, Value)>,
259    pub subselection: SelectionSet,
260}
261
262impl std::ops::Index<InputValueDefinitions> for FederatedGraph {
263    type Output = [InputValueDefinition];
264
265    fn index(&self, index: InputValueDefinitions) -> &Self::Output {
266        let (start, len) = index;
267        &self.input_value_definitions[usize::from(start)..(usize::from(start) + len)]
268    }
269}
270
271impl std::ops::Index<Fields> for FederatedGraph {
272    type Output = [Field];
273
274    fn index(&self, index: Fields) -> &Self::Output {
275        &self.fields[usize::from(index.start)..usize::from(index.end)]
276    }
277}
278
279pub type InputValueDefinitionSet = Vec<InputValueDefinitionSetItem>;
280
281#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, PartialOrd)]
282pub struct InputValueDefinitionSetItem {
283    pub input_value_definition: InputValueDefinitionId,
284    pub subselection: InputValueDefinitionSet,
285}
286
287/// A (start, end) range in FederatedGraph::fields.
288pub type Fields = Range<FieldId>;
289/// A (start, len) range in FederatedSchema.
290pub type InputValueDefinitions = (InputValueDefinitionId, usize);
291
292pub const NO_INPUT_VALUE_DEFINITION: InputValueDefinitions = (InputValueDefinitionId::const_from_usize(0), 0);
293pub const NO_FIELDS: Fields = Range {
294    start: FieldId::const_from_usize(0),
295    end: FieldId::const_from_usize(0),
296};
297
298pub type FieldSet = Vec<FieldSetItem>;
299
300#[derive(Clone, PartialEq, PartialOrd)]
301pub struct FieldSetItem {
302    pub field: FieldId,
303    pub arguments: Vec<(InputValueDefinitionId, Value)>,
304    pub subselection: FieldSet,
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn override_label() {
313        assert!("".parse::<OverrideLabel>().is_err());
314        assert!("percent(heh)".parse::<OverrideLabel>().is_err());
315        assert!("percent(30".parse::<OverrideLabel>().is_err());
316
317        assert_eq!(
318            "percent(30)".parse::<OverrideLabel>().unwrap().as_percent().unwrap(),
319            30
320        );
321    }
322}