graphql_query/schema/
schema_reference.rs1use crate::{
2 ast::OperationKind,
3 error::{Error, Result},
4};
5
6use super::{
7 OutputType, Schema, SchemaField, SchemaFields, SchemaInterfaces, SchemaObject,
8 SchemaPossibleTypes,
9};
10
11#[derive(Clone)]
18pub struct SchemaReference<'a> {
19 output_stack: Vec<OutputType<'a>>,
20 pointer: OutputType<'a>,
21 schema: &'a Schema<'a>,
22}
23
24impl<'a> SchemaReference<'a> {
25 #[inline]
27 pub fn from_object_type(schema: &'a Schema, object: &'a SchemaObject<'a>) -> Self {
28 SchemaReference {
29 schema,
30 output_stack: Vec::with_capacity(32),
31 pointer: OutputType::Object(object),
32 }
33 }
34
35 #[inline]
37 pub fn from_fragment(schema: &'a Schema<'a>, type_condition: &'a str) -> Result<Self> {
38 let typename = schema.get_type(type_condition);
39 match typename {
40 Some(typename) => Ok(SchemaReference {
41 schema,
42 output_stack: Vec::with_capacity(32),
43 pointer: typename
44 .output_type()
45 .expect("This should be an object-output-type."),
46 }),
47 None => Err(Error::new(
48 "Schema does not support given type-condition.",
49 None,
50 )),
51 }
52 }
53
54 #[inline]
56 pub fn from_schema(schema: &'a Schema<'a>, operation_kind: OperationKind) -> Result<Self> {
57 Ok(SchemaReference {
58 schema,
59 output_stack: Vec::with_capacity(32),
60 pointer: OutputType::Object(schema.get_root_type(operation_kind).ok_or_else(|| {
61 Error::new(
62 "Schema does not support selected root operation type.",
63 None,
64 )
65 })?),
66 })
67 }
68
69 #[inline(always)]
70 fn push_pointer(&mut self, pointer: OutputType<'a>) {
71 self.output_stack.push(self.pointer);
72 self.pointer = pointer;
73 }
74
75 #[inline]
77 pub fn leave_type(&mut self) -> Result<OutputType<'a>> {
78 if let Some(pointer) = self.output_stack.pop() {
79 self.pointer = pointer;
80 Ok(pointer)
81 } else {
82 Err(Error::new("Cannot leave root type.", None))
83 }
84 }
85
86 #[inline]
88 pub fn output_type(&self) -> OutputType<'a> {
89 self.pointer
90 }
91
92 #[inline]
94 pub fn get_field(&self, field_name: &'a str) -> Option<&SchemaField<'a>> {
95 match self.pointer {
96 OutputType::Object(object) => object.get_field(field_name),
97 OutputType::Interface(interface) => interface.get_field(field_name),
98 _ => None,
99 }
100 }
101
102 #[inline]
105 pub fn select_field(&mut self, field_name: &'a str) -> Result<OutputType<'a>> {
106 let fields = match self.pointer {
107 OutputType::Object(object) => Ok(object.get_fields()),
108 OutputType::Interface(interface) => Ok(interface.get_fields()),
109 _ => Err(Error::new(
110 "Cannot select fields on non-object/interface type.",
111 None,
112 )),
113 }?;
114 if let Some(field) = fields.get(field_name) {
115 let output_type = field
116 .output_type
117 .of_type(self.schema)
118 .output_type()
119 .expect("This to be an output type.");
120 self.push_pointer(output_type);
121 Ok(output_type)
122 } else {
123 Err(Error::new("Cannot select unknown fields.", None))
124 }
125 }
126
127 pub fn select_condition(&mut self, type_name: &'a str) -> Result<OutputType<'a>> {
130 match self.pointer {
131 OutputType::Object(schema_object) if schema_object.name == type_name => {
132 self.push_pointer(self.pointer);
133 Ok(self.pointer)
134 }
135 OutputType::Object(schema_object) => {
136 let maybe_interface = schema_object
137 .get_interfaces()
138 .into_iter()
139 .find(|interface| *interface == type_name)
140 .map(|x| {
141 self.schema
142 .get_type(x)
143 .expect("The type to exist")
144 .output_type()
145 .expect("and it to be an output type.")
146 });
147 if let Some(output_type) = maybe_interface {
148 self.push_pointer(output_type);
149 Ok(output_type)
150 } else {
151 Err(Error::new(
152 "No possible interface type found for spread name.",
153 None,
154 ))
155 }
156 }
157 OutputType::Interface(interface) => {
158 let maybe_interface = interface
159 .get_interfaces()
160 .into_iter()
161 .chain(interface.get_possible_interfaces())
162 .find(|interface| *interface == type_name)
163 .map(|x| {
164 self.schema
165 .get_type(x)
166 .expect("The type to exist")
167 .output_type()
168 .expect("and it to be an output type.")
169 });
170 if let Some(output_type) = maybe_interface {
171 self.push_pointer(output_type);
172 return Ok(output_type);
173 };
174 let maybe_object = interface
175 .get_possible_types()
176 .into_iter()
177 .find(|object| *object == type_name)
178 .map(|x| {
179 self.schema
180 .get_type(x)
181 .expect("The type to exist")
182 .output_type()
183 .expect("and it to be an output type.")
184 });
185 if let Some(output_type) = maybe_object {
186 self.push_pointer(output_type);
187 Ok(output_type)
188 } else {
189 Err(Error::new(
190 "No possible interface/object type found for spread name.",
191 None,
192 ))
193 }
194 }
195 OutputType::Union(schema_union) => {
196 let maybe_object = schema_union.get_possible_type(type_name).map(|x| {
197 self.schema
198 .get_type(x)
199 .expect("The type to exist")
200 .output_type()
201 .expect("and it to be an output type.")
202 });
203 if let Some(output_type) = maybe_object {
204 self.push_pointer(output_type);
205 Ok(output_type)
206 } else {
207 Err(Error::new(
208 "No possible object type found for spread name.",
209 None,
210 ))
211 }
212 }
213 OutputType::Scalar(_) | OutputType::Enum(_) => Err(Error::new(
214 "Cannot spread fragment on non-abstract type.",
215 None,
216 )),
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::super::{BuildClientSchema, IntrospectionQuery};
224 use super::*;
225 use crate::ast::ASTContext;
226
227 #[test]
228 fn walk_schema() {
229 let ctx = ASTContext::new();
230 let introspection_json = include_str!("../../fixture/introspection_query.json");
231 let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
232 let schema = introspection.build_client_schema(&ctx);
233
234 let mut schema_ref = SchemaReference::from_schema(schema, OperationKind::Query).unwrap();
235
236 assert_eq!(schema_ref.output_type().name(), "query_root");
237
238 let todo_type = schema_ref.select_field("todos").unwrap();
239 assert_eq!(todo_type.name(), "Todo");
240
241 let author_type = schema_ref.select_field("author").unwrap();
242 assert_eq!(author_type.name(), "Author");
243
244 assert_eq!(schema_ref.leave_type().unwrap().name(), "Todo");
245 assert_eq!(schema_ref.leave_type().unwrap().name(), "query_root");
246 }
247
248 #[test]
249 fn selecting_type_repeatedly() {
250 let ctx = ASTContext::new();
251 let introspection_json = include_str!("../../fixture/introspection_query.json");
252 let introspection: IntrospectionQuery = serde_json::from_str(introspection_json).unwrap();
253 let schema = introspection.build_client_schema(&ctx);
254
255 let mut schema_ref = SchemaReference::from_schema(schema, OperationKind::Mutation).unwrap();
256
257 assert_eq!(schema_ref.output_type().name(), "mutation_root");
258
259 let mutation_type = schema_ref.select_condition("mutation_root").unwrap();
260 assert_eq!(mutation_type.name(), "mutation_root");
261
262 assert_eq!(schema_ref.leave_type().unwrap().name(), "mutation_root");
264 assert_eq!(
266 schema_ref.leave_type().unwrap_err().message,
267 "Cannot leave root type."
268 );
269 }
270}