graphql_query/schema/
build_client_schema.rs1use 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);
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>) -> T;
98 }
99
100 impl<'arena> BuildSchemaType<'arena, SchemaType<'arena>> for IntrospectionType<'arena> {
101 #[inline]
102 fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaType<'arena> {
103 match self {
104 IntrospectionType::Scalar(scalar) => {
105 SchemaType::Scalar(ctx.ctx.alloc(scalar.on_create(ctx)))
106 }
107 IntrospectionType::Object(object) => {
108 SchemaType::Object(ctx.ctx.alloc(object.on_create(ctx)))
109 }
110 IntrospectionType::Interface(interface) => {
111 SchemaType::Interface(ctx.ctx.alloc(interface.on_create(ctx)))
112 }
113 IntrospectionType::Union(union_type) => {
114 SchemaType::Union(ctx.ctx.alloc(union_type.on_create(ctx)))
115 }
116 IntrospectionType::Enum(enum_type) => {
117 SchemaType::Enum(ctx.ctx.alloc(enum_type.on_create(ctx)))
118 }
119 IntrospectionType::InputObject(input_object) => {
120 SchemaType::InputObject(ctx.ctx.alloc(input_object.on_create(ctx)))
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>) -> 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>) -> 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>) -> 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>) -> 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>) -> 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 schema_interface_type.add_possible_type(ctx.ctx, name);
250 }
251
252 schema_interface_type
253 }
254 }
255
256 impl<'arena> BuildSchemaType<'arena, SchemaInputObject<'arena>>
257 for IntrospectionInputObjectType<'arena>
258 {
259 #[inline]
260 fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaInputObject<'arena> {
261 let name = ctx.ctx.alloc_str(self.name);
262 let mut input = SchemaInputObject::new(ctx.ctx, name);
263 for field in self.input_fields.iter() {
264 let field_name = ctx.ctx.alloc_str(field.name);
265 let input_field =
266 SchemaInputField::new(field_name, from_input_type_ref(ctx.ctx, &field.of_type));
267 input.add_field(ctx.ctx, input_field);
268 }
269
270 input
271 }
272 }
273}
274
275pub trait BuildClientSchema<'arena> {
276 fn build_client_schema(&self, ctx: &'arena ASTContext) -> &'arena Schema<'arena>;
278}
279
280impl<'arena> BuildClientSchema<'arena> for IntrospectionSchema<'arena> {
281 fn build_client_schema(&self, ctx: &'arena ASTContext) -> &'arena Schema<'arena> {
283 let builder_ctx: &mut BuildSchemaContext =
284 ctx.arena.alloc(private::BuildSchemaContext::new(ctx));
285 builder_ctx.build_schema(self)
286 }
287}
288
289impl<'arena> BuildClientSchema<'arena> for IntrospectionQuery<'arena> {
290 fn build_client_schema(&self, ctx: &'arena ASTContext) -> &'arena Schema<'arena> {
292 self.schema.build_client_schema(ctx)
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::super::schema::{SchemaFields, SchemaPossibleTypes, SchemaSuperType};
299 use super::*;
300
301 #[test]
302 fn build_schema() {
303 let ctx = ASTContext::new();
304 let introspection_json = include_str!("../../fixture/introspection_query.json");
305 let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
306 let schema = introspection.build_client_schema(&ctx);
307
308 let query_root_name = schema.query_type.map(|obj| obj.name).unwrap();
309 assert_eq!(query_root_name, "query_root");
310
311 assert!(std::ptr::eq(
312 schema
313 .get_type(query_root_name)
314 .and_then(|t| t.object())
315 .unwrap(),
316 schema.query_type.unwrap()
317 ));
318 }
319
320 #[test]
321 fn schema_fields() {
322 let ctx = ASTContext::new();
323 let introspection_json = include_str!("../../fixture/introspection_query.json");
324 let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
325 let schema = introspection.build_client_schema(&ctx);
326
327 let todo_type = schema.get_type("Todo").and_then(|t| t.object()).unwrap();
328 let author_type = schema.get_type("Author").unwrap();
329
330 todo_type.get_field("id").unwrap();
331 todo_type.get_field("text").unwrap();
332
333 let author_field = todo_type.get_field("author").unwrap();
334 let maybe_author_type = author_field.output_type.of_type(schema);
335 assert!(schema.is_sub_type(*author_type, *maybe_author_type));
336 assert!(author_type == maybe_author_type);
337 }
338
339 #[test]
340 fn schema_abstract_relationships() {
341 let ctx = ASTContext::new();
342 let introspection_json = include_str!("../../fixture/introspection_query.json");
343 let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
344 let schema = introspection.build_client_schema(&ctx);
345
346 let type_itodo = schema
347 .get_type("ITodo")
348 .and_then(|t| t.interface())
349 .unwrap();
350 let type_bigtodo = schema.get_type("BigTodo").and_then(|t| t.object()).unwrap();
351 let type_smalltodo = schema
352 .get_type("SmallTodo")
353 .and_then(|t| t.object())
354 .unwrap();
355 let type_search = schema
356 .get_type("Search")
357 .and_then(|t| t.union_type())
358 .unwrap();
359
360 assert!(type_search.get_possible_type("SmallTodo").is_some());
361 assert!(type_search.get_possible_type("BigTodo").is_some());
362
363 assert!(type_search.is_sub_type(type_smalltodo.into()));
364 assert!(type_search.is_sub_type(type_bigtodo.into()));
365
366 assert!(type_itodo.is_sub_type(type_smalltodo.into()));
367 assert!(type_itodo.is_sub_type(type_bigtodo.into()));
368
369 assert!(type_itodo.get_possible_type(type_bigtodo.name).is_some());
370 assert!(type_itodo.get_possible_type(type_smalltodo.name).is_some());
371 }
372}