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 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 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 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 fn build_client_schema(&self, ctx: &'arena ASTContext) -> &'arena Schema<'arena>;
292}
293
294impl<'arena> BuildClientSchema<'arena> for IntrospectionSchema<'arena> {
295 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 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}