trustfall_core/ir/
mod.rs

1//! Trustfall intermediate representation (IR)
2
3use std::{
4    borrow::Cow,
5    cmp::Ordering,
6    collections::BTreeMap,
7    fmt::Debug,
8    num::NonZeroUsize,
9    ops::Index,
10    sync::{Arc, OnceLock},
11};
12
13use serde::{Deserialize, Serialize};
14
15pub use self::indexed::{EdgeKind, IndexedQuery, InvalidIRQueryError, Output};
16pub use self::types::{NamedTypedValue, Type};
17pub use self::value::{FieldValue, TransparentValue};
18
19mod indexed;
20mod types;
21pub mod value;
22
23pub(crate) const TYPENAME_META_FIELD: &str = "__typename";
24
25static TYPENAME_META_FIELD_ARC: OnceLock<Arc<str>> = OnceLock::new();
26
27pub(crate) fn get_typename_meta_field() -> &'static Arc<str> {
28    TYPENAME_META_FIELD_ARC.get_or_init(|| Arc::from(TYPENAME_META_FIELD))
29}
30
31/// Unique vertex ID identifying a specific vertex in a Trustfall query
32#[doc(alias("vertex", "node"))]
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
34pub struct Vid(pub(crate) NonZeroUsize);
35
36impl Vid {
37    pub fn new(id: NonZeroUsize) -> Vid {
38        Vid(id)
39    }
40}
41
42/// Unique edge ID identifying a specific edge in a Trustfall query
43#[doc(alias = "edge")]
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
45pub struct Eid(pub(crate) NonZeroUsize);
46
47impl Eid {
48    pub fn new(id: NonZeroUsize) -> Eid {
49        Eid(id)
50    }
51}
52
53/// Parameter values for an edge expansion.
54///
55/// Passed as an argument to the [`Adapter::resolve_starting_vertices`] and
56/// [`Adapter::resolve_neighbors`] functions. In those cases, the caller guarantees that
57/// all edge parameters marked as required in the schema are included in
58/// the [`EdgeParameters`] value.
59///
60/// [`Adapter::resolve_starting_vertices`]: crate::interpreter::Adapter::resolve_neighbors
61/// [`Adapter::resolve_neighbors`]: crate::interpreter::Adapter::resolve_neighbors
62#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
63pub struct EdgeParameters {
64    pub(crate) contents: Arc<BTreeMap<Arc<str>, FieldValue>>,
65}
66
67impl EdgeParameters {
68    pub(crate) fn new(contents: Arc<BTreeMap<Arc<str>, FieldValue>>) -> Self {
69        Self { contents }
70    }
71
72    /// Gets the value of the edge parameter by this name.
73    ///
74    /// Returns `None` if the edge parameter was not present.
75    /// Returns the default value if the query did not set a value but the edge parameter
76    /// defined a default value.
77    pub fn get(&self, name: &str) -> Option<&FieldValue> {
78        self.contents.get(name)
79    }
80
81    /// Iterates through all the edge parameters and their values.
82    pub fn iter(&self) -> impl Iterator<Item = (&'_ Arc<str>, &'_ FieldValue)> + '_ {
83        self.contents.iter()
84    }
85
86    /// Returns `true` if the edge has any parameters, and `false` otherwise.
87    pub fn is_empty(&self) -> bool {
88        self.contents.is_empty()
89    }
90}
91
92/// Enable indexing into [`EdgeParameters`] values: `parameters["param_name"]`
93impl<'a> Index<&'a str> for EdgeParameters {
94    type Output = FieldValue;
95
96    fn index(&self, index: &'a str) -> &Self::Output {
97        &self.contents[index]
98    }
99}
100
101/// A complete component of a query; may itself contain one or more components.
102///
103/// Contains information about the Vid where the component is rooted,
104/// as well as well as maps of all vertices, edges, folds, and outputs from this component.
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct IRQueryComponent {
107    /// The [Vid] of the root, or entry point, of the component.
108    pub root: Vid,
109
110    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
111    pub vertices: BTreeMap<Vid, IRVertex>,
112
113    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
114    pub edges: BTreeMap<Eid, Arc<IREdge>>,
115
116    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
117    pub folds: BTreeMap<Eid, Arc<IRFold>>,
118
119    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
120    pub outputs: BTreeMap<Arc<str>, ContextField>,
121}
122
123/// Intermediate representation of a query
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct IRQuery {
126    pub root_name: Arc<str>,
127
128    #[serde(default, skip_serializing_if = "EdgeParameters::is_empty")]
129    pub root_parameters: EdgeParameters,
130
131    pub root_component: Arc<IRQueryComponent>,
132
133    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
134    pub variables: BTreeMap<Arc<str>, Type>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct IREdge {
139    pub eid: Eid,
140    pub from_vid: Vid,
141    pub to_vid: Vid,
142    pub edge_name: Arc<str>,
143
144    #[serde(default, skip_serializing_if = "EdgeParameters::is_empty")]
145    pub parameters: EdgeParameters,
146
147    /// Indicating if this edge is optional.
148    ///
149    /// Corresponds to the `@optional` directive.
150    #[serde(default = "default_optional", skip_serializing_if = "is_false")]
151    pub optional: bool,
152
153    #[serde(default, skip_serializing_if = "Option::is_none")]
154    pub recursive: Option<Recursive>,
155}
156
157fn default_optional() -> bool {
158    false
159}
160
161fn is_false(b: &bool) -> bool {
162    !b
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166pub struct Recursive {
167    pub depth: NonZeroUsize,
168
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub coerce_to: Option<Arc<str>>,
171}
172
173impl Recursive {
174    pub fn new(depth: NonZeroUsize, coerce_to: Option<Arc<str>>) -> Self {
175        Self { depth, coerce_to }
176    }
177}
178
179/// Representation of a vertex (node) in the Trustfall intermediate
180/// representation (IR).
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182pub struct IRVertex {
183    pub vid: Vid,
184
185    /// The name of the type of the vertex as a string.
186    pub type_name: Arc<str>,
187
188    #[serde(default, skip_serializing_if = "Option::is_none")]
189    pub coerced_from_type: Option<Arc<str>>,
190
191    #[serde(default, skip_serializing_if = "Vec::is_empty")]
192    pub filters: Vec<Operation<LocalField, Argument>>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
196pub struct IRFold {
197    pub eid: Eid,
198    pub from_vid: Vid,
199    pub to_vid: Vid,
200    pub edge_name: Arc<str>,
201
202    #[serde(default, skip_serializing_if = "EdgeParameters::is_empty")]
203    pub parameters: EdgeParameters,
204
205    pub component: Arc<IRQueryComponent>,
206
207    /// Tags from the directly-enclosing component whose values are needed
208    /// inside this fold's component or one of its subcomponents.
209    #[serde(default, skip_serializing_if = "Vec::is_empty")]
210    pub imported_tags: Vec<FieldRef>,
211
212    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
213    pub fold_specific_outputs: BTreeMap<Arc<str>, FoldSpecificFieldKind>,
214
215    #[serde(default, skip_serializing_if = "Vec::is_empty")]
216    pub post_filters: Vec<Operation<FoldSpecificFieldKind, Argument>>,
217}
218
219#[non_exhaustive]
220#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
221pub enum FoldSpecificFieldKind {
222    Count, // Represents the number of elements in an IRFold's component.
223}
224
225static NON_NULL_INT_TYPE: OnceLock<Type> = OnceLock::new();
226
227impl FoldSpecificFieldKind {
228    pub fn field_type(&self) -> &Type {
229        match self {
230            Self::Count => NON_NULL_INT_TYPE.get_or_init(|| Type::new_named_type("Int", false)),
231        }
232    }
233
234    pub fn field_name(&self) -> &str {
235        match self {
236            FoldSpecificFieldKind::Count => "@fold.count",
237        }
238    }
239
240    pub fn transform_suffix(&self) -> &str {
241        match self {
242            FoldSpecificFieldKind::Count => "count",
243        }
244    }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248pub struct FoldSpecificField {
249    // uniquely identifies the fold
250    pub fold_eid: Eid,
251
252    // used to quickly check whether the fold exists at all,
253    // e.g. for "tagged parameter is optional and missing" purposes
254    pub fold_root_vid: Vid,
255
256    pub kind: FoldSpecificFieldKind,
257}
258
259#[non_exhaustive]
260#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
261pub enum TransformationKind {
262    Count,
263}
264
265#[non_exhaustive]
266#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
267pub enum FieldRef {
268    ContextField(ContextField),
269    FoldSpecificField(FoldSpecificField),
270}
271
272impl Ord for FieldRef {
273    fn cmp(&self, other: &Self) -> Ordering {
274        match (self, other) {
275            (FieldRef::ContextField(f1), FieldRef::ContextField(f2)) => f1
276                .vertex_id
277                .cmp(&f2.vertex_id)
278                .then(f1.field_name.as_ref().cmp(f2.field_name.as_ref())),
279            (FieldRef::ContextField(_), FieldRef::FoldSpecificField(_)) => Ordering::Less,
280            (FieldRef::FoldSpecificField(_), FieldRef::ContextField(_)) => Ordering::Greater,
281            (FieldRef::FoldSpecificField(f1), FieldRef::FoldSpecificField(f2)) => {
282                f1.fold_eid.cmp(&f2.fold_eid).then(f1.kind.cmp(&f2.kind))
283            }
284        }
285    }
286}
287
288impl PartialOrd for FieldRef {
289    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
290        Some(self.cmp(other))
291    }
292}
293
294impl From<ContextField> for FieldRef {
295    fn from(c: ContextField) -> Self {
296        Self::ContextField(c)
297    }
298}
299
300impl From<FoldSpecificField> for FieldRef {
301    fn from(f: FoldSpecificField) -> Self {
302        Self::FoldSpecificField(f)
303    }
304}
305
306impl FieldRef {
307    pub fn field_type(&self) -> &Type {
308        match self {
309            FieldRef::ContextField(c) => &c.field_type,
310            FieldRef::FoldSpecificField(f) => f.kind.field_type(),
311        }
312    }
313
314    pub fn field_name(&self) -> &str {
315        match self {
316            FieldRef::ContextField(c) => c.field_name.as_ref(),
317            FieldRef::FoldSpecificField(f) => f.kind.field_name(),
318        }
319    }
320
321    /// The vertex ID at which this reference is considered defined.
322    pub fn defined_at(&self) -> Vid {
323        match self {
324            FieldRef::ContextField(c) => c.vertex_id,
325            FieldRef::FoldSpecificField(f) => f.fold_root_vid,
326        }
327    }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
331pub enum Argument {
332    Tag(FieldRef),
333    Variable(VariableRef),
334}
335
336impl Argument {
337    pub(crate) fn as_tag(&self) -> Option<&FieldRef> {
338        match self {
339            Argument::Tag(t) => Some(t),
340            Argument::Variable(_) => None,
341        }
342    }
343
344    pub(crate) fn evaluate_statically<'a>(
345        &self,
346        query_variables: &'a BTreeMap<Arc<str>, FieldValue>,
347    ) -> Option<Cow<'a, FieldValue>> {
348        match self {
349            Argument::Tag(..) => None,
350            Argument::Variable(var) => {
351                Some(Cow::Borrowed(&query_variables[var.variable_name.as_ref()]))
352            }
353        }
354    }
355}
356
357/// Operations that can be made in the graph.
358///
359/// In a Trustfall query, the `@filter` directive produces `Operation` values:
360/// ```graphql
361/// name @filter(op: "=", value: ["$input"])
362/// ```
363/// would produce the `Operation::Equals` variant, for example.
364#[non_exhaustive]
365#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
366pub enum Operation<LeftT, RightT>
367where
368    LeftT: Debug + Clone + PartialEq + Eq,
369    RightT: Debug + Clone + PartialEq + Eq,
370{
371    IsNull(LeftT),
372    IsNotNull(LeftT),
373    Equals(LeftT, RightT),
374    NotEquals(LeftT, RightT),
375    LessThan(LeftT, RightT),
376    LessThanOrEqual(LeftT, RightT),
377    GreaterThan(LeftT, RightT),
378    GreaterThanOrEqual(LeftT, RightT),
379    Contains(LeftT, RightT),
380    NotContains(LeftT, RightT),
381    OneOf(LeftT, RightT),
382    NotOneOf(LeftT, RightT),
383    HasPrefix(LeftT, RightT),
384    NotHasPrefix(LeftT, RightT),
385    HasSuffix(LeftT, RightT),
386    NotHasSuffix(LeftT, RightT),
387    HasSubstring(LeftT, RightT),
388    NotHasSubstring(LeftT, RightT),
389    RegexMatches(LeftT, RightT),
390    NotRegexMatches(LeftT, RightT),
391}
392
393impl<LeftT, RightT> Operation<LeftT, RightT>
394where
395    LeftT: Debug + Clone + PartialEq + Eq,
396    RightT: Debug + Clone + PartialEq + Eq,
397{
398    pub(crate) fn left(&self) -> &LeftT {
399        match self {
400            Operation::IsNull(left) => left,
401            Operation::IsNotNull(left) => left,
402            Operation::Equals(left, _) => left,
403            Operation::NotEquals(left, _) => left,
404            Operation::LessThan(left, _) => left,
405            Operation::LessThanOrEqual(left, _) => left,
406            Operation::GreaterThan(left, _) => left,
407            Operation::GreaterThanOrEqual(left, _) => left,
408            Operation::Contains(left, _) => left,
409            Operation::NotContains(left, _) => left,
410            Operation::OneOf(left, _) => left,
411            Operation::NotOneOf(left, _) => left,
412            Operation::HasPrefix(left, _) => left,
413            Operation::NotHasPrefix(left, _) => left,
414            Operation::HasSuffix(left, _) => left,
415            Operation::NotHasSuffix(left, _) => left,
416            Operation::HasSubstring(left, _) => left,
417            Operation::NotHasSubstring(left, _) => left,
418            Operation::RegexMatches(left, _) => left,
419            Operation::NotRegexMatches(left, _) => left,
420        }
421    }
422
423    pub(crate) fn right(&self) -> Option<&RightT> {
424        match self {
425            Operation::IsNull(_) | Operation::IsNotNull(_) => None,
426            Operation::Equals(_, right) => Some(right),
427            Operation::NotEquals(_, right) => Some(right),
428            Operation::LessThan(_, right) => Some(right),
429            Operation::LessThanOrEqual(_, right) => Some(right),
430            Operation::GreaterThan(_, right) => Some(right),
431            Operation::GreaterThanOrEqual(_, right) => Some(right),
432            Operation::Contains(_, right) => Some(right),
433            Operation::NotContains(_, right) => Some(right),
434            Operation::OneOf(_, right) => Some(right),
435            Operation::NotOneOf(_, right) => Some(right),
436            Operation::HasPrefix(_, right) => Some(right),
437            Operation::NotHasPrefix(_, right) => Some(right),
438            Operation::HasSuffix(_, right) => Some(right),
439            Operation::NotHasSuffix(_, right) => Some(right),
440            Operation::HasSubstring(_, right) => Some(right),
441            Operation::NotHasSubstring(_, right) => Some(right),
442            Operation::RegexMatches(_, right) => Some(right),
443            Operation::NotRegexMatches(_, right) => Some(right),
444        }
445    }
446
447    /// The operation name, as it would have appeared in the `@filter` directive `op` argument.
448    pub(crate) fn operation_name(&self) -> &'static str {
449        match self {
450            Operation::IsNull(..) => "is_null",
451            Operation::IsNotNull(..) => "is_not_null",
452            Operation::Equals(..) => "=",
453            Operation::NotEquals(..) => "!=",
454            Operation::LessThan(..) => "<",
455            Operation::LessThanOrEqual(..) => "<=",
456            Operation::GreaterThan(..) => ">",
457            Operation::GreaterThanOrEqual(..) => ">=",
458            Operation::Contains(..) => "contains",
459            Operation::NotContains(..) => "not_contains",
460            Operation::OneOf(..) => "one_of",
461            Operation::NotOneOf(..) => "not_one_of",
462            Operation::HasPrefix(..) => "has_prefix",
463            Operation::NotHasPrefix(..) => "not_has_prefix",
464            Operation::HasSuffix(..) => "has_suffix",
465            Operation::NotHasSuffix(..) => "not_has_suffix",
466            Operation::HasSubstring(..) => "has_substring",
467            Operation::NotHasSubstring(..) => "not_has_substring",
468            Operation::RegexMatches(..) => "regex",
469            Operation::NotRegexMatches(..) => "not_regex",
470        }
471    }
472
473    pub(crate) fn map<'a, LeftF, LeftOutT, RightF, RightOutT>(
474        &'a self,
475        map_left: LeftF,
476        map_right: RightF,
477    ) -> Operation<LeftOutT, RightOutT>
478    where
479        LeftOutT: Debug + Clone + PartialEq + Eq,
480        RightOutT: Debug + Clone + PartialEq + Eq,
481        LeftF: FnOnce(&'a LeftT) -> LeftOutT,
482        RightF: FnOnce(&'a RightT) -> RightOutT,
483    {
484        match self {
485            Operation::IsNull(left) => Operation::IsNull(map_left(left)),
486            Operation::IsNotNull(left) => Operation::IsNotNull(map_left(left)),
487            Operation::Equals(left, right) => Operation::Equals(map_left(left), map_right(right)),
488            Operation::NotEquals(left, right) => {
489                Operation::NotEquals(map_left(left), map_right(right))
490            }
491            Operation::LessThan(left, right) => {
492                Operation::LessThan(map_left(left), map_right(right))
493            }
494            Operation::LessThanOrEqual(left, right) => {
495                Operation::LessThanOrEqual(map_left(left), map_right(right))
496            }
497            Operation::GreaterThan(left, right) => {
498                Operation::GreaterThan(map_left(left), map_right(right))
499            }
500            Operation::GreaterThanOrEqual(left, right) => {
501                Operation::GreaterThanOrEqual(map_left(left), map_right(right))
502            }
503            Operation::Contains(left, right) => {
504                Operation::Contains(map_left(left), map_right(right))
505            }
506            Operation::NotContains(left, right) => {
507                Operation::NotContains(map_left(left), map_right(right))
508            }
509            Operation::OneOf(left, right) => Operation::OneOf(map_left(left), map_right(right)),
510            Operation::NotOneOf(left, right) => {
511                Operation::NotOneOf(map_left(left), map_right(right))
512            }
513            Operation::HasPrefix(left, right) => {
514                Operation::HasPrefix(map_left(left), map_right(right))
515            }
516            Operation::NotHasPrefix(left, right) => {
517                Operation::NotHasPrefix(map_left(left), map_right(right))
518            }
519            Operation::HasSuffix(left, right) => {
520                Operation::HasSuffix(map_left(left), map_right(right))
521            }
522            Operation::NotHasSuffix(left, right) => {
523                Operation::NotHasSuffix(map_left(left), map_right(right))
524            }
525            Operation::HasSubstring(left, right) => {
526                Operation::HasSubstring(map_left(left), map_right(right))
527            }
528            Operation::NotHasSubstring(left, right) => {
529                Operation::NotHasSubstring(map_left(left), map_right(right))
530            }
531            Operation::RegexMatches(left, right) => {
532                Operation::RegexMatches(map_left(left), map_right(right))
533            }
534            Operation::NotRegexMatches(left, right) => {
535                Operation::NotRegexMatches(map_left(left), map_right(right))
536            }
537        }
538    }
539
540    pub(crate) fn try_map<LeftF, LeftOutT, RightF, RightOutT, Err>(
541        &self,
542        map_left: LeftF,
543        map_right: RightF,
544    ) -> Result<Operation<LeftOutT, RightOutT>, Err>
545    where
546        LeftOutT: Debug + Clone + PartialEq + Eq,
547        RightOutT: Debug + Clone + PartialEq + Eq,
548        LeftF: FnOnce(&LeftT) -> Result<LeftOutT, Err>,
549        RightF: FnOnce(&RightT) -> Result<RightOutT, Err>,
550    {
551        Ok(match self {
552            Operation::IsNull(left) => Operation::IsNull(map_left(left)?),
553            Operation::IsNotNull(left) => Operation::IsNotNull(map_left(left)?),
554            Operation::Equals(left, right) => Operation::Equals(map_left(left)?, map_right(right)?),
555            Operation::NotEquals(left, right) => {
556                Operation::NotEquals(map_left(left)?, map_right(right)?)
557            }
558            Operation::LessThan(left, right) => {
559                Operation::LessThan(map_left(left)?, map_right(right)?)
560            }
561            Operation::LessThanOrEqual(left, right) => {
562                Operation::LessThanOrEqual(map_left(left)?, map_right(right)?)
563            }
564            Operation::GreaterThan(left, right) => {
565                Operation::GreaterThan(map_left(left)?, map_right(right)?)
566            }
567            Operation::GreaterThanOrEqual(left, right) => {
568                Operation::GreaterThanOrEqual(map_left(left)?, map_right(right)?)
569            }
570            Operation::Contains(left, right) => {
571                Operation::Contains(map_left(left)?, map_right(right)?)
572            }
573            Operation::NotContains(left, right) => {
574                Operation::NotContains(map_left(left)?, map_right(right)?)
575            }
576            Operation::OneOf(left, right) => Operation::OneOf(map_left(left)?, map_right(right)?),
577            Operation::NotOneOf(left, right) => {
578                Operation::NotOneOf(map_left(left)?, map_right(right)?)
579            }
580            Operation::HasPrefix(left, right) => {
581                Operation::HasPrefix(map_left(left)?, map_right(right)?)
582            }
583            Operation::NotHasPrefix(left, right) => {
584                Operation::NotHasPrefix(map_left(left)?, map_right(right)?)
585            }
586            Operation::HasSuffix(left, right) => {
587                Operation::HasSuffix(map_left(left)?, map_right(right)?)
588            }
589            Operation::NotHasSuffix(left, right) => {
590                Operation::NotHasSuffix(map_left(left)?, map_right(right)?)
591            }
592            Operation::HasSubstring(left, right) => {
593                Operation::HasSubstring(map_left(left)?, map_right(right)?)
594            }
595            Operation::NotHasSubstring(left, right) => {
596                Operation::NotHasSubstring(map_left(left)?, map_right(right)?)
597            }
598            Operation::RegexMatches(left, right) => {
599                Operation::RegexMatches(map_left(left)?, map_right(right)?)
600            }
601            Operation::NotRegexMatches(left, right) => {
602                Operation::NotRegexMatches(map_left(left)?, map_right(right)?)
603            }
604        })
605    }
606}
607
608#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
609pub struct ContextField {
610    pub vertex_id: Vid,
611
612    pub field_name: Arc<str>,
613
614    pub field_type: Type,
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
618pub struct LocalField {
619    pub field_name: Arc<str>,
620
621    pub field_type: Type,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
625pub struct VariableRef {
626    pub variable_name: Arc<str>,
627
628    pub variable_type: Type,
629}
630
631#[cfg(test)]
632mod tests {
633    use std::sync::Arc;
634
635    use super::FieldValue;
636
637    fn serialize_then_deserialize(value: &FieldValue) -> FieldValue {
638        ron::from_str(ron::to_string(value).unwrap().as_str()).unwrap()
639    }
640
641    #[test]
642    fn serialize_then_deserialize_enum() {
643        let value = FieldValue::Enum("foo".into());
644        let deserialized: FieldValue = serialize_then_deserialize(&value);
645        assert_eq!(value, deserialized, "Serialized as: {}", ron::to_string(&value).unwrap());
646    }
647
648    #[test]
649    fn serialize_then_deserialize_list() {
650        let value = FieldValue::List(Arc::new([
651            FieldValue::Int64(1),
652            FieldValue::Int64(2),
653            FieldValue::String("foo".into()),
654        ]));
655        let deserialized: FieldValue = serialize_then_deserialize(&value);
656        assert_eq!(value, deserialized, "Serialized as: {}", ron::to_string(&value).unwrap());
657    }
658
659    #[test]
660    fn serialize_then_deserialize_float() {
661        let value = FieldValue::Float64(1.0);
662        let deserialized: FieldValue = serialize_then_deserialize(&value);
663        assert_eq!(value, deserialized, "Serialized as: {}", ron::to_string(&value).unwrap());
664    }
665
666    #[test]
667    fn serialize_then_deserialize_i64() {
668        let value = FieldValue::Int64(-123);
669        let deserialized: FieldValue = serialize_then_deserialize(&value);
670        assert_eq!(value, deserialized, "Serialized as: {}", ron::to_string(&value).unwrap());
671    }
672
673    #[test]
674    fn serialize_then_deserialize_u64() {
675        let value = FieldValue::Uint64((i64::MAX as u64) + 1);
676        let deserialized: FieldValue = serialize_then_deserialize(&value);
677        assert_eq!(value, deserialized, "Serialized as: {}", ron::to_string(&value).unwrap());
678    }
679}