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