trustfall_core/interpreter/mod.rs
1use std::{collections::BTreeMap, fmt::Debug, sync::Arc};
2
3use itertools::Itertools;
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5
6use crate::{
7 ir::{EdgeParameters, Eid, FieldRef, FieldValue, IndexedQuery, Type, Vid},
8 util::BTreeMapTryInsertExt,
9};
10
11use self::error::QueryArgumentsError;
12
13pub mod basic_adapter;
14pub mod error;
15pub mod execution;
16mod filtering;
17pub mod helpers;
18mod hints;
19pub mod replay;
20pub mod trace;
21
22pub use hints::{
23 CandidateValue, DynamicallyResolvedValue, EdgeInfo, NeighborInfo, QueryInfo, Range,
24 RequiredProperty, ResolveEdgeInfo, ResolveInfo, VertexInfo,
25};
26
27/// An iterator of vertices representing data points we are querying.
28pub type VertexIterator<'vertex, VertexT> = Box<dyn Iterator<Item = VertexT> + 'vertex>;
29
30/// An iterator of query contexts: bookkeeping structs we use to build up the query results.
31///
32/// Each context represents a possible result of the query. At each query processing step,
33/// all the contexts at that step have fulfilled all the query conditions thus far.
34///
35/// This type is usually an input to adapter resolver functions. Calling those functions
36/// asks them to resolve a property, edge, or type coercion for the particular vertex
37/// the context is currently processing at that point in the query.
38pub type ContextIterator<'vertex, VertexT> = VertexIterator<'vertex, DataContext<VertexT>>;
39
40/// Iterator of (context, outcome) tuples: the output type of most resolver functions.
41///
42/// Resolver functions produce an output value for each context:
43/// - resolve_property() produces that property's value;
44/// - resolve_neighbors() produces an iterator of neighboring vertices along an edge;
45/// - resolve_coercion() gives a bool representing whether the vertex is of the desired type.
46///
47/// This type lets us write those output types in a slightly more readable way.
48pub type ContextOutcomeIterator<'vertex, VertexT, OutcomeT> =
49 Box<dyn Iterator<Item = (DataContext<VertexT>, OutcomeT)> + 'vertex>;
50
51/// Accessor method for the `__typename` special property of Trustfall vertices.
52pub trait Typename {
53 /// Returns the type name of this vertex in the Trustfall query graph.
54 ///
55 /// Corresponds to the `__typename` special property of Trustfall vertices.
56 fn typename(&self) -> &'static str;
57}
58
59/// A tagged value captured and imported from another query component.
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub(crate) enum TaggedValue {
62 /// This tagged value comes from an @optional scope that didn't exist.
63 /// All comparisons against it should succeed, per our spec.
64 NonexistentOptional,
65
66 /// This tagged value was resolved to the specified value.
67 Some(FieldValue),
68}
69
70/// A partial result of a Trustfall query within the interpreter defined in this module.
71#[derive(Debug, Clone)]
72pub struct DataContext<Vertex> {
73 active_vertex: Option<Vertex>,
74 vertices: BTreeMap<Vid, Option<Vertex>>,
75 values: Vec<FieldValue>,
76 suspended_vertices: Vec<Option<Vertex>>,
77 folded_contexts: BTreeMap<Eid, Option<Vec<DataContext<Vertex>>>>,
78 folded_values: BTreeMap<(Eid, Arc<str>), Option<ValueOrVec>>,
79 piggyback: Option<Vec<DataContext<Vertex>>>,
80 imported_tags: BTreeMap<FieldRef, TaggedValue>,
81}
82
83impl<Vertex> DataContext<Vertex> {
84 /// The vertex currently being processed.
85 ///
86 /// For contexts passed to an [`Adapter`] resolver method,
87 /// this is the vertex whose data needs to be resolved.
88 ///
89 /// The active vertex may be `None` when processing an `@optional` part
90 /// of a Trustfall query whose data did not exist. In that case:
91 /// - [`Adapter::resolve_property`] must produce [`FieldValue::Null`] for that context.
92 /// - [`Adapter::resolve_neighbors`] must produce an empty iterator of neighbors
93 /// such as `Box::new(std::iter::empty())` for that context.
94 /// - [`Adapter::resolve_coercion`] must produce a `false` coercion outcome for that context.
95 pub fn active_vertex<V>(&self) -> Option<&V>
96 where
97 Vertex: AsVertex<V>,
98 {
99 self.active_vertex.as_ref().and_then(AsVertex::as_vertex)
100 }
101
102 /// Whether this context is currently inside an `@optional` block with no values.
103 ///
104 /// Let's unpack that:
105 /// - At this point in the query execution, we're within an `@optional` block of the query.
106 /// - An `@optional` edge in the evaluation of this context did not exist. It might not be
107 /// the innermost `@optional` one — it might be a prior one if we're several `@optional` deep.
108 ///
109 /// This is relevant, for example, for situations where we have filters or type coercions
110 /// to apply within the `@optional` block and need to know if there's anything to filter/coerce.
111 #[inline]
112 pub(crate) fn within_nonexistent_optional(&self) -> bool {
113 self.active_vertex.is_none()
114 }
115
116 /// Converts `DataContext<Vertex>` to `DataContext<Other>` by mapping each `Vertex` to `Other`.
117 ///
118 /// If you are implementing an [`Adapter`] for a data source,
119 /// you almost certainly *should not* be using this function.
120 /// You're probably looking for [`DataContext::active_vertex()`] instead.
121 pub fn map<Other>(self, mapper: &mut impl FnMut(Vertex) -> Other) -> DataContext<Other> {
122 DataContext {
123 active_vertex: self.active_vertex.map(&mut *mapper),
124 vertices: self.vertices.into_iter().map(|(k, v)| (k, v.map(&mut *mapper))).collect(),
125 values: self.values,
126 suspended_vertices: self
127 .suspended_vertices
128 .into_iter()
129 .map(|v| v.map(&mut *mapper))
130 .collect(),
131 folded_contexts: self
132 .folded_contexts
133 .into_iter()
134 .map(|(k, ctxs)| {
135 (k, ctxs.map(|v| v.into_iter().map(|ctx| ctx.map(&mut *mapper)).collect()))
136 })
137 .collect(),
138 folded_values: self.folded_values,
139 piggyback: self
140 .piggyback
141 .map(|v| v.into_iter().map(|ctx| ctx.map(&mut *mapper)).collect()),
142 imported_tags: self.imported_tags,
143 }
144 }
145
146 /// Map each `Vertex` to `Option<Other>`, thus converting `Self` to `DataContext<Other>`.
147 ///
148 /// This is the [`DataContext`] equivalent of [`Option::and_then`][option], which is also
149 /// referred to as "flat-map" in some languages.
150 ///
151 /// If you are implementing an [`Adapter`] for a data source,
152 /// you almost certainly *should not* be using this function.
153 /// You're probably looking for [`DataContext::active_vertex()`] instead.
154 ///
155 /// [option]: https://doc.rust-lang.org/std/option/enum.Option.html#method.and_then
156 pub fn flat_map<T>(self, mapper: &mut impl FnMut(Vertex) -> Option<T>) -> DataContext<T> {
157 DataContext {
158 active_vertex: self.active_vertex.and_then(&mut *mapper),
159 vertices: self
160 .vertices
161 .into_iter()
162 .map(|(k, v)| (k, v.and_then(&mut *mapper)))
163 .collect::<BTreeMap<Vid, Option<T>>>(),
164 values: self.values,
165 suspended_vertices: self
166 .suspended_vertices
167 .into_iter()
168 .map(|v| v.and_then(&mut *mapper))
169 .collect(),
170 folded_contexts: self
171 .folded_contexts
172 .into_iter()
173 .map(|(k, ctxs)| {
174 (k, ctxs.map(|v| v.into_iter().map(|ctx| ctx.flat_map(&mut *mapper)).collect()))
175 })
176 .collect(),
177 folded_values: self.folded_values,
178 piggyback: self
179 .piggyback
180 .map(|v| v.into_iter().map(|ctx| ctx.flat_map(&mut *mapper)).collect()),
181 imported_tags: self.imported_tags,
182 }
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187enum ValueOrVec {
188 Value(FieldValue),
189 Vec(Vec<ValueOrVec>),
190}
191
192impl ValueOrVec {
193 fn as_mut_vec(&mut self) -> Option<&mut Vec<ValueOrVec>> {
194 match self {
195 ValueOrVec::Value(_) => None,
196 ValueOrVec::Vec(v) => Some(v),
197 }
198 }
199}
200
201impl From<ValueOrVec> for FieldValue {
202 fn from(v: ValueOrVec) -> Self {
203 match v {
204 ValueOrVec::Value(value) => value,
205 ValueOrVec::Vec(v) => v.into(),
206 }
207 }
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(bound = "Vertex: Debug + Clone + Serialize + DeserializeOwned")]
212struct SerializableContext<Vertex> {
213 active_vertex: Option<Vertex>,
214 vertices: BTreeMap<Vid, Option<Vertex>>,
215
216 #[serde(default, skip_serializing_if = "Vec::is_empty")]
217 values: Vec<FieldValue>,
218
219 #[serde(default, skip_serializing_if = "Vec::is_empty")]
220 suspended_vertices: Vec<Option<Vertex>>,
221
222 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
223 folded_contexts: BTreeMap<Eid, Option<Vec<DataContext<Vertex>>>>,
224
225 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
226 folded_values: BTreeMap<(Eid, Arc<str>), Option<ValueOrVec>>,
227
228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 piggyback: Option<Vec<DataContext<Vertex>>>,
230
231 /// Tagged values imported from an ancestor component of the one currently being evaluated.
232 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
233 imported_tags: BTreeMap<FieldRef, TaggedValue>,
234}
235
236impl<Vertex> From<SerializableContext<Vertex>> for DataContext<Vertex> {
237 fn from(context: SerializableContext<Vertex>) -> Self {
238 Self {
239 active_vertex: context.active_vertex,
240 vertices: context.vertices,
241 values: context.values,
242 suspended_vertices: context.suspended_vertices,
243 folded_contexts: context.folded_contexts,
244 folded_values: context.folded_values,
245 piggyback: context.piggyback,
246 imported_tags: context.imported_tags,
247 }
248 }
249}
250
251impl<Vertex> From<DataContext<Vertex>> for SerializableContext<Vertex> {
252 fn from(context: DataContext<Vertex>) -> Self {
253 Self {
254 active_vertex: context.active_vertex,
255 vertices: context.vertices,
256 values: context.values,
257 suspended_vertices: context.suspended_vertices,
258 folded_contexts: context.folded_contexts,
259 folded_values: context.folded_values,
260 piggyback: context.piggyback,
261 imported_tags: context.imported_tags,
262 }
263 }
264}
265
266impl<Vertex: Clone + Debug> DataContext<Vertex> {
267 pub fn new(vertex: Option<Vertex>) -> DataContext<Vertex> {
268 DataContext {
269 active_vertex: vertex,
270 piggyback: None,
271 vertices: Default::default(),
272 values: Default::default(),
273 suspended_vertices: Default::default(),
274 folded_contexts: Default::default(),
275 folded_values: Default::default(),
276 imported_tags: Default::default(),
277 }
278 }
279
280 fn record_vertex(&mut self, vid: Vid) {
281 self.vertices.insert_or_error(vid, self.active_vertex.clone()).unwrap();
282 }
283
284 fn activate_vertex(self, vid: &Vid) -> DataContext<Vertex> {
285 DataContext {
286 active_vertex: self.vertices[vid].clone(),
287 vertices: self.vertices,
288 values: self.values,
289 suspended_vertices: self.suspended_vertices,
290 folded_contexts: self.folded_contexts,
291 folded_values: self.folded_values,
292 piggyback: self.piggyback,
293 imported_tags: self.imported_tags,
294 }
295 }
296
297 fn split_and_move_to_vertex(&self, new_vertex: Option<Vertex>) -> DataContext<Vertex> {
298 DataContext {
299 active_vertex: new_vertex,
300 vertices: self.vertices.clone(),
301 values: self.values.clone(),
302 suspended_vertices: self.suspended_vertices.clone(),
303 folded_contexts: self.folded_contexts.clone(),
304 folded_values: self.folded_values.clone(),
305 piggyback: None,
306 imported_tags: self.imported_tags.clone(),
307 }
308 }
309
310 fn move_to_vertex(self, new_vertex: Option<Vertex>) -> DataContext<Vertex> {
311 DataContext {
312 active_vertex: new_vertex,
313 vertices: self.vertices,
314 values: self.values,
315 suspended_vertices: self.suspended_vertices,
316 folded_contexts: self.folded_contexts,
317 folded_values: self.folded_values,
318 piggyback: self.piggyback,
319 imported_tags: self.imported_tags,
320 }
321 }
322
323 fn ensure_suspended(mut self) -> DataContext<Vertex> {
324 if let Some(vertex) = self.active_vertex {
325 self.suspended_vertices.push(Some(vertex));
326 DataContext {
327 active_vertex: None,
328 vertices: self.vertices,
329 values: self.values,
330 suspended_vertices: self.suspended_vertices,
331 folded_contexts: self.folded_contexts,
332 folded_values: self.folded_values,
333 piggyback: self.piggyback,
334 imported_tags: self.imported_tags,
335 }
336 } else {
337 self
338 }
339 }
340
341 fn ensure_unsuspended(mut self) -> DataContext<Vertex> {
342 match self.active_vertex {
343 None => {
344 let active_vertex = self.suspended_vertices.pop().unwrap();
345 DataContext {
346 active_vertex,
347 vertices: self.vertices,
348 values: self.values,
349 suspended_vertices: self.suspended_vertices,
350 folded_contexts: self.folded_contexts,
351 folded_values: self.folded_values,
352 piggyback: self.piggyback,
353 imported_tags: self.imported_tags,
354 }
355 }
356 Some(_) => self,
357 }
358 }
359}
360
361impl<Vertex: PartialEq> PartialEq for DataContext<Vertex> {
362 fn eq(&self, other: &Self) -> bool {
363 self.active_vertex == other.active_vertex
364 && self.vertices == other.vertices
365 && self.values == other.values
366 && self.suspended_vertices == other.suspended_vertices
367 && self.folded_contexts == other.folded_contexts
368 && self.piggyback == other.piggyback
369 && self.imported_tags == other.imported_tags
370 }
371}
372
373impl<Vertex: Eq> Eq for DataContext<Vertex> {}
374
375impl<Vertex> Serialize for DataContext<Vertex>
376where
377 Vertex: Debug + Clone + Serialize + DeserializeOwned,
378{
379 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
380 where
381 S: serde::Serializer,
382 {
383 // TODO: eventually maybe write a proper (de)serialize?
384 SerializableContext::from(self.clone()).serialize(serializer)
385 }
386}
387
388impl<'de, Vertex> Deserialize<'de> for DataContext<Vertex>
389where
390 Vertex: Debug + Clone + Serialize + DeserializeOwned,
391{
392 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
393 where
394 D: serde::Deserializer<'de>,
395 {
396 // TODO: eventually maybe write a proper (de)serialize?
397 SerializableContext::deserialize(deserializer).map(DataContext::from)
398 }
399}
400
401#[derive(Debug, Clone, PartialEq, Eq)]
402pub struct InterpretedQuery {
403 pub indexed_query: Arc<IndexedQuery>,
404 pub arguments: Arc<BTreeMap<Arc<str>, FieldValue>>,
405}
406
407impl InterpretedQuery {
408 #[inline]
409 pub fn from_query_and_arguments(
410 indexed_query: Arc<IndexedQuery>,
411 arguments: Arc<BTreeMap<Arc<str>, FieldValue>>,
412 ) -> Result<Self, QueryArgumentsError> {
413 let mut errors = vec![];
414
415 let mut missing_arguments = vec![];
416 for (variable_name, variable_type) in &indexed_query.ir_query.variables {
417 match arguments.get(variable_name) {
418 Some(argument_value) => {
419 // Ensure the provided argument value is valid for the variable's inferred type.
420 if let Err(e) = validate_argument_type(
421 variable_name.as_ref(),
422 variable_type,
423 argument_value,
424 ) {
425 errors.push(e);
426 }
427 }
428 None => {
429 missing_arguments.push(variable_name.as_ref());
430 }
431 }
432 }
433 if !missing_arguments.is_empty() {
434 errors.push(QueryArgumentsError::MissingArguments(
435 missing_arguments.into_iter().map(|x| x.to_string()).collect(),
436 ));
437 }
438
439 let unused_arguments = arguments
440 .keys()
441 .map(|x| x.as_ref())
442 .filter(|arg| !indexed_query.ir_query.variables.contains_key(*arg))
443 .collect_vec();
444 if !unused_arguments.is_empty() {
445 errors.push(QueryArgumentsError::UnusedArguments(
446 unused_arguments.into_iter().map(|x| x.to_string()).collect(),
447 ));
448 }
449
450 if errors.is_empty() {
451 Ok(Self { indexed_query, arguments })
452 } else {
453 Err(errors.into())
454 }
455 }
456}
457
458fn validate_argument_type(
459 variable_name: &str,
460 variable_type: &Type,
461 argument_value: &FieldValue,
462) -> Result<(), QueryArgumentsError> {
463 if variable_type.is_valid_value(argument_value) {
464 Ok(())
465 } else {
466 Err(QueryArgumentsError::ArgumentTypeError(
467 variable_name.to_string(),
468 variable_type.to_string(),
469 argument_value.to_owned(),
470 ))
471 }
472}
473
474/// Trustfall data providers implement this trait to enable querying their data sets.
475///
476/// The most straightforward way to implement this trait is to use
477/// the [`trustfall_stubgen` code-generator tool][stubgen] tool to auto-generate stubs
478/// customized to match your dataset's schema, then fill in the blanks denoted by `todo!()`.
479///
480/// If you prefer to implement the trait without code generation, consider implementing
481/// [`BasicAdapter`](self::basic_adapter::BasicAdapter) instead. That's a simpler version
482/// of this trait and can be faster to implement without a significant loss of functionality:
483/// - Both traits support the same set of queries. Under the hood,
484/// [`BasicAdapter`](self::basic_adapter::BasicAdapter) itself implements [`Adapter`].
485/// - If you need optimizations like batching or caching, you can implement them within
486/// [`BasicAdapter`](self::basic_adapter::BasicAdapter) as well.
487/// - If you need more advanced optimizations such as predicate pushdown, or need to access
488/// Trustfall's static analysis capabilities, implement this trait directly instead.
489///
490/// [stubgen]: https://docs.rs/trustfall_stubgen/latest/trustfall_stubgen/
491pub trait Adapter<'vertex> {
492 /// The type of vertices in the dataset this adapter queries.
493 /// Unless your intended vertex type is cheap to clone, consider wrapping it an [`Rc`][rc]
494 /// or [`Arc`] to make cloning it cheaper since that's a fairly common operation
495 /// when queries are evaluated.
496 ///
497 /// [rc]: std::rc::Rc
498 type Vertex: Clone + Debug + 'vertex;
499
500 /// Produce an iterator of vertices for the specified starting edge.
501 ///
502 /// Starting edges are the entry points for querying according to a schema.
503 /// Each query starts at such an edge, and such starting edges are defined
504 /// directly on the root query type of the schema.
505 ///
506 /// # Example
507 ///
508 /// Consider this query which gets the URLs of the posts
509 /// currently on the front page of HackerNews: [playground][playground]
510 /// ```graphql
511 /// query {
512 /// FrontPage {
513 /// url @output
514 /// }
515 /// }
516 /// ```
517 ///
518 /// The [HackerNews schema][schema] defines `FrontPage` as a starting edge
519 /// that points to vertices of type `Item`.
520 ///
521 /// As part of executing this query, Trustfall will call this method
522 /// with `edge_name = "FrontPage"`. Here's the [implementation of this method][method]
523 /// in the HackerNews example adapter.
524 ///
525 /// # Preconditions and postconditions
526 ///
527 /// The caller guarantees that:
528 /// - The specified edge is a starting edge in the schema being queried.
529 /// - Any parameters the edge requires per the schema have values provided.
530 ///
531 /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-HackerNews-item-URLs-of-the-items*l*3-currently-on-the-front-page.*lquery---0FrontPage---2url-*o*l--_0*J*l*J&v=--0*l*J
532 /// [schema]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/hackernews.graphql#L35
533 /// [method]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/adapter.rs#L128-L134
534 fn resolve_starting_vertices(
535 &self,
536 edge_name: &Arc<str>,
537 parameters: &EdgeParameters,
538 resolve_info: &ResolveInfo,
539 ) -> VertexIterator<'vertex, Self::Vertex>;
540
541 /// Resolve a property required by the query that's being evaluated.
542 ///
543 /// Each [`DataContext`] in the `contexts` parameter has an active vertex
544 /// [`DataContext::active_vertex()`]. This call is asking for the value of
545 /// the specified property on each such active vertex,
546 /// for each active vertex in the input iterator.
547 ///
548 /// The most ergonomic way to implement this method is usually via
549 /// the [`resolve_property_with()`][resolve-property] helper method together with
550 /// the [`field_property!()`][field-property] and [`accessor_property!()`][accessor-property]
551 /// macros.
552 ///
553 /// # Example
554 ///
555 /// Consider this query which gets the URLs of the posts
556 /// currently on the front page of HackerNews: [playground][playground]
557 /// ```graphql
558 /// query {
559 /// FrontPage {
560 /// url @output
561 /// }
562 /// }
563 /// ```
564 ///
565 /// Our HackerNews schema [defines][starting-edge] `FrontPage` as a starting edge
566 /// that points to vertices of type `Item`, and [defines][property] `url`
567 /// as a property on the `Item` type.
568 ///
569 /// As part of executing this query, Trustfall will call this method
570 /// with `type_name = "Item"` and `property_name = "url"`.
571 /// This is how Trustfall looks up the URLs of the items returned by this query.
572 /// Here's the [implementation of this method][method] in the HackerNews example adapter.
573 ///
574 /// # Preconditions and postconditions
575 ///
576 /// The active vertex may be `None`, or a `Some(v)` whose `v` is of Rust type `&Self::Vertex`
577 /// and represents a vertex whose type in the Trustfall schema is given by
578 /// this function's `type_name` parameter.
579 ///
580 /// The caller guarantees that:
581 /// - `type_name` is a type or interface defined in the schema.
582 /// - `property_name` is either a property field on `type_name` defined in the schema,
583 /// or the special value `"__typename"` requesting the name of the vertex's type.
584 /// - When the active vertex is `Some(...)`, its represents a vertex of type `type_name`:
585 /// either its type is exactly `type_name`, or `type_name` is an interface implemented by
586 /// the vertex's type.
587 ///
588 /// The returned iterator must satisfy these properties:
589 /// - Produce `(context, property_value)` tuples with the property's value for that context.
590 /// - Produce contexts in the same order as the input `contexts` iterator produced them.
591 /// - Produce property values whose type matches the property's type defined in the schema.
592 /// - When a context's active vertex is `None`, its property value is [`FieldValue::Null`].
593 ///
594 /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-HackerNews-item-URLs-of-the-items*l*3-currently-on-the-front-page.*lquery---0FrontPage---2url-*o*l--_0*J*l*J&v=--0*l*J
595 /// [starting-edge]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/hackernews.graphql#L35
596 /// [property]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/hackernews.graphql#L44
597 /// [method]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/adapter.rs#L151
598 /// [resolve-property]: helpers::resolve_property_with
599 /// [field-property]: crate::field_property
600 /// [accessor-property]: crate::accessor_property
601 fn resolve_property<V: AsVertex<Self::Vertex> + 'vertex>(
602 &self,
603 contexts: ContextIterator<'vertex, V>,
604 type_name: &Arc<str>,
605 property_name: &Arc<str>,
606 resolve_info: &ResolveInfo,
607 ) -> ContextOutcomeIterator<'vertex, V, FieldValue>;
608
609 /// Resolve the neighboring vertices across an edge.
610 ///
611 /// Each [`DataContext`] in the `contexts` parameter has an active vertex
612 /// [`DataContext::active_vertex()`]. This call is asking for
613 /// the iterator of neighboring vertices of the active vertex along a specified edge,
614 /// for each active vertex in the input iterator.
615 ///
616 /// The most ergonomic way to implement this method is usually via
617 /// the [`resolve_neighbors_with()`][resolve-neighbors] helper method.
618 ///
619 /// # Example
620 ///
621 /// Consider this query which gets the usernames and karma points of the users
622 /// who submitted the latest stories on HackerNews: [playground][playground]
623 /// ```graphql
624 /// query {
625 /// Latest {
626 /// byUser {
627 /// id @output
628 /// karma @output
629 /// }
630 /// }
631 /// }
632 /// ```
633 ///
634 /// Our HackerNews schema [defines][starting-edge] `Latest` as a starting edge
635 /// that points to vertices of type `Story`.
636 /// In turn, `Story` [has an edge][edge] called `byUser` that points to `User` vertices.
637 ///
638 /// As part of executing this query, Trustfall will call this method
639 /// with `type_name = "Story"` and `edge_name = "byUser"`.
640 /// This is how Trustfall looks up the user vertices representing the submitters
641 /// of the latest HackerNews stories.
642 /// Here's the [implementation of this method][method] in the HackerNews example adapter.
643 ///
644 /// # Preconditions and postconditions
645 ///
646 /// The active vertex may be `None`, or a `Some(v)` whose `v` is of Rust type `&Self::Vertex`
647 /// and represents a vertex whose type in the Trustfall schema is given by
648 /// this function's `type_name` parameter.
649 ///
650 /// If the schema this adapter covers has no edges aside from starting edges,
651 /// then this method will never be called and may be implemented as `unreachable!()`.
652 ///
653 /// The caller guarantees that:
654 /// - `type_name` is a type or interface defined in the schema.
655 /// - `edge_name` is an edge field on `type_name` defined in the schema.
656 /// - Each parameter required by the edge has a value of appropriate type, per the schema.
657 /// - When the active vertex is `Some(...)`, its represents a vertex of type `type_name`:
658 /// either its type is exactly `type_name`, or `type_name` is an interface implemented by
659 /// the vertex's type.
660 ///
661 /// The returned iterator must satisfy these properties:
662 /// - Produce `(context, neighbors)` tuples with an iterator of neighbor vertices for that edge.
663 /// - Produce contexts in the same order as the input `contexts` iterator produced them.
664 /// - Each neighboring vertex is of the type specified for that edge in the schema.
665 /// - When a context's active vertex is None, it has an empty neighbors iterator.
666 ///
667 /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-usernames-and-karma-points-of-the-folks*l*3-who-submitted-the-latest-stories-on-HackerNews.*lquery---0Latest---2byUser---4id-*o*l--_4karma-*o*l--_2--*0*J*l*J&v=--0*l*J
668 /// [starting-edge]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/hackernews.graphql#L37
669 /// [edge]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/hackernews.graphql#L73
670 /// [method]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/adapter.rs#L225
671 /// [resolve-neighbors]: helpers::resolve_neighbors_with
672 fn resolve_neighbors<V: AsVertex<Self::Vertex> + 'vertex>(
673 &self,
674 contexts: ContextIterator<'vertex, V>,
675 type_name: &Arc<str>,
676 edge_name: &Arc<str>,
677 parameters: &EdgeParameters,
678 resolve_info: &ResolveEdgeInfo,
679 ) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, Self::Vertex>>;
680
681 /// Attempt to coerce vertices to a subtype, as required by the query that's being evaluated.
682 ///
683 /// Each [`DataContext`] in the `contexts` parameter has an active vertex
684 /// [`DataContext::active_vertex()`]. This call is asking whether the active vertex
685 /// happens to be an instance of a subtype, for each active vertex in the input iterator.
686 ///
687 /// The most ergonomic ways to implement this method usually rely on
688 /// the [`resolve_coercion_using_schema()`][resolve-schema]
689 /// or [`resolve_coercion_with()`][resolve-basic] helper methods.
690 ///
691 /// # Example
692 ///
693 /// Consider this query which gets the titles of all stories on the front page of HackerNews,
694 /// while discarding non-story items such as job postings and polls: [playground][playground]
695 /// ```graphql
696 /// query {
697 /// FrontPage {
698 /// ... on Story {
699 /// title @output
700 /// }
701 /// }
702 /// }
703 /// ```
704 ///
705 /// Our HackerNews schema [defines][starting-edge] `FrontPage` as a starting edge
706 /// that points to vertices of type `Item`.
707 /// It also defines `Story` as [a subtype][subtype] of `Item`.
708 ///
709 /// After resolving the `FrontPage` starting edge, Trustfall will need to determine which
710 /// of the resulting `Item` vertices are actually of type `Story`.
711 /// This is when Trustfall will call this method
712 /// with `type_name = "Item"` and `coerce_to_type = "Story"`.
713 /// Here's the [implementation of this method][method] in the HackerNews example adapter.
714 ///
715 /// # Preconditions and postconditions
716 ///
717 /// The active vertex may be `None`, or a `Some(v)` whose `v` is of Rust type `&Self::Vertex`
718 /// and represents a vertex whose type in the Trustfall schema is given by
719 /// this function's `type_name` parameter.
720 ///
721 /// If this adapter's schema contains no subtyping, then no type coercions are possible:
722 /// this method will never be called and may be implemented as `unreachable!()`.
723 ///
724 /// The caller guarantees that:
725 /// - `type_name` is an interface defined in the schema.
726 /// - `coerce_to_type` is a type or interface that implements `type_name` in the schema.
727 /// - When the active vertex is `Some(...)`, its represents a vertex of type `type_name`:
728 /// either its type is exactly `type_name`, or `type_name` is an interface implemented by
729 /// the vertex's type.
730 ///
731 /// The returned iterator must satisfy these properties:
732 /// - Produce `(context, can_coerce)` tuples showing if the coercion succeded for that context.
733 /// - Produce contexts in the same order as the input `contexts` iterator produced them.
734 /// - Each neighboring vertex is of the type specified for that edge in the schema.
735 /// - When a context's active vertex is `None`, its coercion outcome is `false`.
736 ///
737 /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-title-of-stories-on-the-HN-front-page.*l*3-Discards-any-non*-story-items-on-the-front-page*L*l*3-such-as-job-postings-or-polls.*lquery---0FrontPage---2*E-Story---4title-*o*l--_2--*0*J*l*J&v=--0*l*J
738 /// [starting-edge]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/hackernews.graphql#L35
739 /// [subtype]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/hackernews.graphql#L58
740 /// [method]: https://github.com/obi1kenobi/trustfall/blob/trustfall-v0.7.1/trustfall/examples/hackernews/adapter.rs#L377
741 /// [resolve-schema]: helpers::resolve_coercion_using_schema
742 /// [resolve-basic]: helpers::resolve_coercion_with
743 fn resolve_coercion<V: AsVertex<Self::Vertex> + 'vertex>(
744 &self,
745 contexts: ContextIterator<'vertex, V>,
746 type_name: &Arc<str>,
747 coerce_to_type: &Arc<str>,
748 resolve_info: &ResolveInfo,
749 ) -> ContextOutcomeIterator<'vertex, V, bool>;
750}
751
752/// Attempt to dereference a value to a `&V`, returning `None` if the value did not contain a `V`.
753///
754/// This trait allows types that may contain a `V` to be projected down to a `Option<&V>`.
755/// It's similar in spirit to the built-in [`Deref`][deref] trait.
756/// The primary difference is that [`AsVertex`] does not guarantee it'll be able to produce a `&V`,
757/// instead returning `Option<&V>`. The same type may implement [`AsVertex<V>`] multiple times
758/// with different `V` types, also unlike [`Deref`][deref].
759///
760/// [deref]: https://doc.rust-lang.org/std/ops/trait.Deref.html
761pub trait AsVertex<V>: Debug + Clone {
762 /// Dereference this value into a `&V`, if the value happens to contain a `V`.
763 ///
764 /// If this method returns `Some(&v)`, [`AsVertex::into_vertex()`] for the same `V`
765 /// is guaranteed to return `Some(v)` as well.
766 fn as_vertex(&self) -> Option<&V>;
767
768 /// Consume `self` and produce the contained `V`, if one was indeed present.
769 ///
770 /// If this method returned `Some(v)`, prior [`AsVertex::as_vertex()`] calls for the same `V`
771 /// are guaranteed to have returned `Some(&v)` as well.
772 fn into_vertex(self) -> Option<V>;
773}
774
775/// Allow bidirectional conversions between a type `V` and the type implementing this trait.
776///
777/// Values of type `V` may be converted into the type implementing this trait, and values
778/// of the implementing type may be converted into `V` via the `AsVertex<V>` supertrait.
779pub trait Cast<V>: AsVertex<V> {
780 /// Convert a `V` into the type implementing this trait.
781 ///
782 /// This is the inverse of [`AsVertex::into_vertex()`]: this function converts `vertex: V`
783 /// into `Self` whereas [`AsVertex::into_vertex()`] can convert the resulting `Self`
784 /// back into `Some(v)`.
785 fn into_self(vertex: V) -> Self;
786}
787
788/// Trivially, every `Debug + Clone` type is [`AsVertex`] of itself.
789impl<V: Debug + Clone> AsVertex<V> for V {
790 fn as_vertex(&self) -> Option<&V> {
791 Some(self)
792 }
793
794 fn into_vertex(self) -> Option<V> {
795 Some(self)
796 }
797}