Skip to main content

graphql_query/schema/
build_client_schema.rs

1use self::private::BuildSchemaContext;
2
3use super::introspection::{IntrospectionQuery, IntrospectionSchema};
4use super::schema::Schema;
5use crate::ast::ASTContext;
6
7pub(crate) mod private {
8    use hashbrown::{hash_map::DefaultHashBuilder, HashMap};
9
10    use super::super::{introspection::*, schema::*};
11    use super::ASTContext;
12
13    fn from_output_type_ref<'a>(
14        ctx: &'a ASTContext,
15        output: &IntrospectionOutputTypeRef,
16    ) -> &'a TypeRef<'a> {
17        use std::ops::Deref;
18        let out = match output {
19            IntrospectionOutputTypeRef::List { of_type } => {
20                TypeRef::ListType(from_output_type_ref(ctx, of_type.deref()))
21            }
22            IntrospectionOutputTypeRef::NonNull { of_type } => {
23                TypeRef::NonNullType(from_output_type_ref(ctx, of_type.deref()))
24            }
25            IntrospectionOutputTypeRef::ScalarType { name }
26            | IntrospectionOutputTypeRef::EnumType { name }
27            | IntrospectionOutputTypeRef::ObjectType { name }
28            | IntrospectionOutputTypeRef::InterfaceType { name }
29            | IntrospectionOutputTypeRef::UnionType { name } => {
30                // TODO: Check whether type matches here
31                let name = ctx.alloc_str(name);
32                TypeRef::Type(name)
33            }
34        };
35
36        ctx.alloc(out)
37    }
38
39    #[derive(Clone)]
40    pub struct BuildSchemaContext<'arena> {
41        pub(crate) ctx: &'arena ASTContext,
42    }
43
44    impl<'arena> BuildSchemaContext<'arena> {
45        pub(crate) fn new(ctx: &'arena ASTContext) -> Self {
46            BuildSchemaContext { ctx }
47        }
48
49        pub fn build_schema(
50            &'arena self,
51            introspection: &IntrospectionSchema<'arena>,
52        ) -> &'arena Schema<'arena> {
53            // We have created our initial set of types, this ensures that every type is present
54            // before we start building them.
55            let mut schema_types: HashMap<
56                &str,
57                &'arena SchemaType,
58                DefaultHashBuilder,
59                &bumpalo::Bump,
60            > = HashMap::new_in(&self.ctx.arena);
61            for introspection_type in introspection.types.iter() {
62                let schema_type = BuildSchemaType::on_create(introspection_type, self, &introspection.types);
63                schema_types.insert(
64                    self.ctx.alloc_str(introspection_type.name()),
65                    self.ctx.alloc(schema_type),
66                );
67            }
68
69            let query_type = introspection
70                .query_type
71                .as_ref()
72                .and_then(|type_ref| schema_types.get(&type_ref.name))
73                .and_then(|schema_type| schema_type.object());
74
75            let mutation_type = introspection
76                .mutation_type
77                .as_ref()
78                .and_then(|type_ref| schema_types.get(&type_ref.name))
79                .and_then(|schema_type| schema_type.object());
80
81            let subscription_type = introspection
82                .subscription_type
83                .as_ref()
84                .and_then(|type_ref| schema_types.get(&type_ref.name))
85                .and_then(|schema_type| schema_type.object());
86
87            self.ctx.alloc(Schema {
88                query_type,
89                mutation_type,
90                subscription_type,
91                types: schema_types,
92            })
93        }
94    }
95
96    pub trait BuildSchemaType<'arena, T>: Sized {
97        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, introspection_types: &[IntrospectionType<'arena>]) -> T;
98    }
99
100    impl<'arena> BuildSchemaType<'arena, SchemaType<'arena>> for IntrospectionType<'arena> {
101        #[inline]
102        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, introspection_types: &[IntrospectionType<'arena>]) -> SchemaType<'arena> {
103            match self {
104                IntrospectionType::Scalar(scalar) => {
105                    SchemaType::Scalar(ctx.ctx.alloc(scalar.on_create(ctx, introspection_types)))
106                }
107                IntrospectionType::Object(object) => {
108                    SchemaType::Object(ctx.ctx.alloc(object.on_create(ctx, introspection_types)))
109                }
110                IntrospectionType::Interface(interface) => {
111                    SchemaType::Interface(ctx.ctx.alloc(interface.on_create(ctx, introspection_types)))
112                }
113                IntrospectionType::Union(union_type) => {
114                    SchemaType::Union(ctx.ctx.alloc(union_type.on_create(ctx, introspection_types)))
115                }
116                IntrospectionType::Enum(enum_type) => {
117                    SchemaType::Enum(ctx.ctx.alloc(enum_type.on_create(ctx, introspection_types)))
118                }
119                IntrospectionType::InputObject(input_object) => {
120                    SchemaType::InputObject(ctx.ctx.alloc(input_object.on_create(ctx, introspection_types)))
121                }
122            }
123        }
124    }
125
126    impl<'arena> BuildSchemaType<'arena, SchemaScalar<'arena>> for IntrospectionScalarType<'arena> {
127        #[inline]
128        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaScalar<'arena> {
129            SchemaScalar::new(ctx.ctx.alloc_str(self.name))
130        }
131    }
132
133    impl<'arena> BuildSchemaType<'arena, SchemaEnum<'arena>> for IntrospectionEnumType<'arena> {
134        #[inline]
135        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaEnum<'arena> {
136            let name = ctx.ctx.alloc_str(self.name);
137            let mut enum_type = SchemaEnum::new(ctx.ctx, name);
138            for value in self.enum_values.iter() {
139                let value_name = ctx.ctx.alloc_str(value.name);
140                enum_type.add_value(ctx.ctx, value_name);
141            }
142            enum_type
143        }
144    }
145
146    impl<'arena> BuildSchemaType<'arena, SchemaUnion<'arena>> for IntrospectionUnionType<'arena> {
147        #[inline]
148        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaUnion<'arena> {
149            let name = ctx.ctx.alloc_str(self.name);
150            let mut schema_union_type = SchemaUnion::new(ctx.ctx, name);
151            for introspection_type_ref in self.possible_types.possible_types.iter() {
152                let name = ctx.ctx.alloc_str(introspection_type_ref.name);
153                schema_union_type.add_possible_type(ctx.ctx, name);
154            }
155            schema_union_type
156        }
157    }
158
159    fn from_input_type_ref<'arena>(
160        ctx: &'arena ASTContext,
161        input: &IntrospectionInputTypeRef,
162    ) -> &'arena TypeRef<'arena> {
163        use std::ops::Deref;
164        let type_ref = match input {
165            IntrospectionInputTypeRef::List { of_type } => {
166                TypeRef::ListType(from_input_type_ref(ctx, of_type.deref()))
167            }
168            IntrospectionInputTypeRef::NonNull { of_type } => {
169                TypeRef::NonNullType(from_input_type_ref(ctx, of_type.deref()))
170            }
171            IntrospectionInputTypeRef::ScalarType { name }
172            | IntrospectionInputTypeRef::EnumType { name }
173            | IntrospectionInputTypeRef::InputObjectType { name } => {
174                // TODO: Check whether type matches here
175                let name = ctx.alloc_str(name);
176                TypeRef::Type(name)
177            }
178        };
179
180        ctx.alloc(type_ref)
181    }
182
183    impl<'arena> BuildSchemaType<'arena, SchemaObject<'arena>> for IntrospectionObjectType<'arena> {
184        #[inline]
185        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaObject<'arena> {
186            let name = ctx.ctx.alloc_str(self.name);
187            let mut schema_object_type = SchemaObject::new(ctx.ctx, name);
188            for field in self.implementation.fields.iter() {
189                let field_name = ctx.ctx.alloc_str(field.name);
190                let mut schema_field = SchemaField::new(
191                     ctx.ctx,
192                    field_name,
193                    from_output_type_ref(ctx.ctx, &field.of_type),
194                );
195
196                for arg in field.args.iter() {
197                    let arg_name = ctx.ctx.alloc_str(arg.name);
198                    let input_field =
199                        SchemaInputField::new(arg_name, from_input_type_ref(ctx.ctx, &arg.of_type));
200                    schema_field.add_argument(ctx.ctx, input_field);
201                }
202
203                schema_object_type.add_field(ctx.ctx, schema_field);
204            }
205
206            if let Some(interfaces) = &self.implementation.interfaces {
207                for introspection_type_ref in interfaces.iter() {
208                    let name = ctx.ctx.alloc_str(introspection_type_ref.name);
209                    schema_object_type.add_interface(ctx.ctx, name);
210                }
211            }
212            schema_object_type
213        }
214    }
215
216    impl<'arena> BuildSchemaType<'arena, SchemaInterface<'arena>>
217        for IntrospectionInterfaceType<'arena>
218    {
219        #[inline]
220        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, introspection_types: &[IntrospectionType<'arena>]) -> SchemaInterface<'arena> {
221            let name = ctx.ctx.alloc_str(self.name);
222            let mut schema_interface_type = SchemaInterface::new(ctx.ctx, name);
223
224            for field in self.implementation.fields.iter() {
225                let field_name = ctx.ctx.alloc_str(field.name);
226                let mut schema_field = SchemaField::new(
227                    ctx.ctx,
228                    field_name,
229                    from_output_type_ref(ctx.ctx, &field.of_type),
230                );
231                for arg in field.args.iter() {
232                    let arg_name = ctx.ctx.alloc_str(arg.name);
233                    let input_field =
234                        SchemaInputField::new(arg_name, from_input_type_ref(ctx.ctx, &arg.of_type));
235                    schema_field.add_argument(ctx.ctx, input_field);
236                }
237                schema_interface_type.add_field(ctx.ctx, schema_field);
238            }
239
240            if let Some(interfaces) = &self.implementation.interfaces {
241                for introspection_type_ref in interfaces.iter() {
242                    let name = ctx.ctx.alloc_str(introspection_type_ref.name);
243                    schema_interface_type.add_interface(ctx.ctx, name);
244                }
245            }
246
247            for introspection_type_ref in self.possible_types.possible_types.iter() {
248                let name = ctx.ctx.alloc_str(introspection_type_ref.name);
249                if let Some(kind) = introspection_type_ref.kind {
250                    if kind == "INTERFACE" {
251                        schema_interface_type.add_possible_interface(ctx.ctx, name);
252                    } else {
253                        schema_interface_type.add_possible_type(ctx.ctx, name);
254                    }
255                } else {
256                    let introspection_type = introspection_types.iter().find(|f| f.name() == name);
257                    if let Some(IntrospectionType::Interface(_)) = introspection_type {
258                        schema_interface_type.add_possible_interface(ctx.ctx, name);
259                    } else {
260                        schema_interface_type.add_possible_type(ctx.ctx, name);
261                    }
262                }
263
264            }
265
266            schema_interface_type
267        }
268    }
269
270    impl<'arena> BuildSchemaType<'arena, SchemaInputObject<'arena>>
271        for IntrospectionInputObjectType<'arena>
272    {
273        #[inline]
274        fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaInputObject<'arena> {
275            let name = ctx.ctx.alloc_str(self.name);
276            let mut input = SchemaInputObject::new(ctx.ctx, name);
277            for field in self.input_fields.iter() {
278                let field_name = ctx.ctx.alloc_str(field.name);
279                let input_field =
280                    SchemaInputField::new(field_name, from_input_type_ref(ctx.ctx, &field.of_type));
281                input.add_field(ctx.ctx, input_field);
282            }
283
284            input
285        }
286    }
287}
288
289pub trait BuildClientSchema<'arena> {
290    /// Converts the introspected data to a [Schema].
291    fn build_client_schema(&self, ctx: &'arena ASTContext) -> &'arena Schema<'arena>;
292}
293
294impl<'arena> BuildClientSchema<'arena> for IntrospectionSchema<'arena> {
295    /// Converts the introspected data to a [Schema].
296    fn build_client_schema(&self, ctx: &'arena ASTContext) -> &'arena Schema<'arena> {
297        let builder_ctx: &mut BuildSchemaContext =
298            ctx.arena.alloc(private::BuildSchemaContext::new(ctx));
299        builder_ctx.build_schema(self)
300    }
301}
302
303impl<'arena> BuildClientSchema<'arena> for IntrospectionQuery<'arena> {
304    /// Converts the introspected data to a [Schema].
305    fn build_client_schema(&self, ctx: &'arena ASTContext) -> &'arena Schema<'arena> {
306        self.schema.build_client_schema(ctx)
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::super::schema::{SchemaFields, SchemaPossibleTypes, SchemaSuperType};
313    use super::*;
314
315    #[test]
316    fn build_schema() {
317        let ctx = ASTContext::new();
318        let introspection_json = include_str!("../../fixture/introspection_query.json");
319        let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
320        let schema = introspection.build_client_schema(&ctx);
321
322        let query_root_name = schema.query_type.map(|obj| obj.name).unwrap();
323        assert_eq!(query_root_name, "query_root");
324
325        assert!(std::ptr::eq(
326            schema
327                .get_type(query_root_name)
328                .and_then(|t| t.object())
329                .unwrap(),
330            schema.query_type.unwrap()
331        ));
332    }
333
334    #[test]
335    fn schema_fields() {
336        let ctx = ASTContext::new();
337        let introspection_json = include_str!("../../fixture/introspection_query.json");
338        let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
339        let schema = introspection.build_client_schema(&ctx);
340
341        let todo_type = schema.get_type("Todo").and_then(|t| t.object()).unwrap();
342        let author_type = schema.get_type("Author").unwrap();
343
344        todo_type.get_field("id").unwrap();
345        todo_type.get_field("text").unwrap();
346
347        let author_field = todo_type.get_field("author").unwrap();
348        let maybe_author_type = author_field.output_type.of_type(schema);
349        assert!(schema.is_sub_type(*author_type, *maybe_author_type));
350        assert!(author_type == maybe_author_type);
351    }
352
353    #[test]
354    fn schema_abstract_relationships() {
355        let ctx = ASTContext::new();
356        let introspection_json = include_str!("../../fixture/introspection_query.json");
357        let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
358        let schema = introspection.build_client_schema(&ctx);
359
360        let type_itodo = schema
361            .get_type("ITodo")
362            .and_then(|t| t.interface())
363            .unwrap();
364        let type_bigtodo = schema.get_type("BigTodo").and_then(|t| t.object()).unwrap();
365        let type_smalltodo = schema
366            .get_type("SmallTodo")
367            .and_then(|t| t.object())
368            .unwrap();
369        let type_search = schema
370            .get_type("Search")
371            .and_then(|t| t.union_type())
372            .unwrap();
373
374        assert!(type_search.get_possible_type("SmallTodo").is_some());
375        assert!(type_search.get_possible_type("BigTodo").is_some());
376
377        assert!(type_search.is_sub_type(type_smalltodo.into()));
378        assert!(type_search.is_sub_type(type_bigtodo.into()));
379
380        assert!(type_itodo.is_sub_type(type_smalltodo.into()));
381        assert!(type_itodo.is_sub_type(type_bigtodo.into()));
382
383        assert!(type_itodo.get_possible_type(type_bigtodo.name).is_some());
384        assert!(type_itodo.get_possible_type(type_smalltodo.name).is_some());
385    }
386}