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}