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                Selection::Typename => {}
244            }
245        }
246        None
247    }
248}
249
250#[derive(Clone, Debug, PartialEq, PartialOrd)]
251pub enum Selection {
252    Typename,
253    Field(FieldSelection),
254    InlineFragment { on: Definition, subselection: SelectionSet },
255}
256
257#[derive(Clone, Debug, PartialEq, PartialOrd)]
258pub struct FieldSelection {
259    pub field_id: FieldId,
260    pub arguments: Vec<(InputValueDefinitionId, Value)>,
261    pub subselection: SelectionSet,
262}
263
264impl std::ops::Index<InputValueDefinitions> for FederatedGraph {
265    type Output = [InputValueDefinition];
266
267    fn index(&self, index: InputValueDefinitions) -> &Self::Output {
268        let (start, len) = index;
269        &self.input_value_definitions[usize::from(start)..(usize::from(start) + len)]
270    }
271}
272
273impl std::ops::Index<Fields> for FederatedGraph {
274    type Output = [Field];
275
276    fn index(&self, index: Fields) -> &Self::Output {
277        &self.fields[usize::from(index.start)..usize::from(index.end)]
278    }
279}
280
281pub type InputValueDefinitionSet = Vec<InputValueDefinitionSetItem>;
282
283#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, PartialOrd)]
284pub struct InputValueDefinitionSetItem {
285    pub input_value_definition: InputValueDefinitionId,
286    pub subselection: InputValueDefinitionSet,
287}
288
289/// A (start, end) range in FederatedGraph::fields.
290pub type Fields = Range<FieldId>;
291/// A (start, len) range in FederatedSchema.
292pub type InputValueDefinitions = (InputValueDefinitionId, usize);
293
294pub const NO_INPUT_VALUE_DEFINITION: InputValueDefinitions = (InputValueDefinitionId::const_from_usize(0), 0);
295pub const NO_FIELDS: Fields = Range {
296    start: FieldId::const_from_usize(0),
297    end: FieldId::const_from_usize(0),
298};
299
300pub type FieldSet = Vec<FieldSetItem>;
301
302#[derive(Clone, PartialEq, PartialOrd)]
303pub struct FieldSetItem {
304    pub field: FieldId,
305    pub arguments: Vec<(InputValueDefinitionId, Value)>,
306    pub subselection: FieldSet,
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn override_label() {
315        assert!("".parse::<OverrideLabel>().is_err());
316        assert!("percent(heh)".parse::<OverrideLabel>().is_err());
317        assert!("percent(30".parse::<OverrideLabel>().is_err());
318
319        assert_eq!(
320            "percent(30)".parse::<OverrideLabel>().unwrap().as_percent().unwrap(),
321            30
322        );
323    }
324}