frunk_core/
path.rs

1//! Holds models, traits, and logic for generic traversal of models
2//!
3//! ```
4//! # use frunk_proc_macros::path;
5//! # use frunk_derives::LabelledGeneric;
6//! # fn main() {
7//! #[derive(LabelledGeneric)]
8//! struct Address<'a> {
9//!     name: &'a str,
10//! }
11//!
12//! #[derive(LabelledGeneric)]
13//! struct User<'a> {
14//!     name: &'a str,
15//!     address: Address<'a>,
16//! }
17//!
18//! let u = User {
19//!   name: "Joe",
20//!   address: Address { name: "blue pond" },
21//! };
22//!
23//! let name_path = path!(name);
24//!
25//! {
26//! let traversed_name = name_path.get(&u);
27//! assert_eq!(*traversed_name, "Joe");
28//! }
29//!
30//! // You can also **add** paths together
31//! let address_path = path!(address);
32//! let address_name_path = address_path + name_path;
33//!
34//! let traversed_address_name = address_name_path.get(u);
35//! assert_eq!(traversed_address_name, "blue pond");
36//! # }
37//! ```
38//!
39//! There is also a Path! type macro that allows you to declare type constraints for
40//! shape-dependent functions on LabelledGeneric types.
41//!
42//! ```
43//! # use frunk_proc_macros::{path, Path};
44//! # use frunk_core::path::PathTraverser;
45//! # use frunk_derives::LabelledGeneric;
46//! # fn main() {
47//! #[derive(LabelledGeneric)]
48//! struct Dog<'a> {
49//!     name: &'a str,
50//!     dimensions: Dimensions,
51//! }
52//!
53//! #[derive(LabelledGeneric)]
54//! struct Cat<'a> {
55//!     name: &'a str,
56//!     dimensions: Dimensions,
57//! }
58//!
59//! #[derive(LabelledGeneric)]
60//! struct Dimensions {
61//!     height: usize,
62//!     width: usize,
63//!     unit: SizeUnit,
64//! }
65//!
66//! #[derive(Debug)]
67//! enum SizeUnit {
68//!     Cm,
69//!     Inch,
70//! }
71//!
72//! let dog = Dog {
73//!     name: "Joe",
74//!     dimensions: Dimensions {
75//!         height: 10,
76//!         width: 5,
77//!         unit: SizeUnit::Inch,
78//!     },
79//! };
80//!
81//! let cat = Cat {
82//!     name: "Schmoe",
83//!     dimensions: Dimensions {
84//!         height: 7,
85//!         width: 3,
86//!         unit: SizeUnit::Cm,
87//!     },
88//! };
89//!
90//! // Prints height as long as `A` has the right "shape" (e.g.
91//! // has `dimensions.height: usize` and `dimension.unit: SizeUnit)
92//! fn print_height<'a, A, HeightIdx, UnitIdx>(obj: &'a A) -> String
93//! where
94//!     &'a A: PathTraverser<Path!(dimensions.height), HeightIdx, TargetValue = &'a usize>
95//!         + PathTraverser<Path!(dimensions.unit), UnitIdx, TargetValue = &'a SizeUnit>,
96//! {
97//!     format!(
98//!         "Height [{} {:?}]",
99//!         path!(dimensions.height).get(obj),
100//!         path!(dimensions.unit).get(obj)
101//!     )
102//! }
103//!
104//! assert_eq!(print_height(&dog), "Height [10 Inch]".to_string());
105//! assert_eq!(print_height(&cat), "Height [7 Cm]".to_string());
106//! # }
107//! ```
108use super::hlist::*;
109use super::labelled::*;
110
111use core::marker::PhantomData;
112use core::ops::Add;
113
114#[derive(Clone, Copy, Debug)]
115pub struct Path<T>(PhantomData<T>);
116
117impl<T> Path<T> {
118    /// Creates a new Path
119    pub fn new() -> Path<T> {
120        Path(PhantomData)
121    }
122
123    /// Gets something using the current path
124    pub fn get<V, I, O>(&self, o: O) -> V
125    where
126        O: PathTraverser<Self, I, TargetValue = V>,
127    {
128        o.get()
129    }
130}
131
132impl<T> Default for Path<T> {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138/// Trait for traversing based on Path
139pub trait PathTraverser<Path, Indices> {
140    type TargetValue;
141
142    /// Returns a pair consisting of the value pointed to by the target key and the remainder.
143    fn get(self) -> Self::TargetValue;
144}
145
146// For the case where we have no more field names to traverse
147impl<Name, PluckIndex, Traversable> PathTraverser<Path<HCons<Name, HNil>>, PluckIndex>
148    for Traversable
149where
150    Traversable: IntoLabelledGeneric,
151    <Traversable as IntoLabelledGeneric>::Repr: ByNameFieldPlucker<Name, PluckIndex>,
152{
153    type TargetValue = <<Traversable as IntoLabelledGeneric>::Repr as ByNameFieldPlucker<
154        Name,
155        PluckIndex,
156    >>::TargetValue;
157
158    #[inline(always)]
159    fn get(self) -> Self::TargetValue {
160        self.into().pluck_by_name().0.value
161    }
162}
163
164// For the case where a path nests another path (e.g. nested traverse)
165impl<HeadName, TailNames, HeadPluckIndex, TailPluckIndices, Traversable>
166    PathTraverser<Path<HCons<HeadName, Path<TailNames>>>, HCons<HeadPluckIndex, TailPluckIndices>>
167    for Traversable
168where
169    Traversable: IntoLabelledGeneric,
170    <Traversable as IntoLabelledGeneric>::Repr: ByNameFieldPlucker<HeadName, HeadPluckIndex>,
171    <<Traversable as IntoLabelledGeneric>::Repr as ByNameFieldPlucker<HeadName, HeadPluckIndex>>::TargetValue:
172        PathTraverser<Path<TailNames>, TailPluckIndices>,
173{
174    type TargetValue = <<<Traversable as IntoLabelledGeneric>::Repr as ByNameFieldPlucker<HeadName, HeadPluckIndex>>::TargetValue as
175    PathTraverser<Path<TailNames>, TailPluckIndices>>::TargetValue ;
176
177    #[inline(always)]
178    fn get(self) -> Self::TargetValue {
179        self.into().pluck_by_name().0.value.get()
180    }
181}
182
183// For the simple case of adding to a single path
184impl<Name, RHSParam> Add<Path<RHSParam>> for Path<HCons<Name, HNil>> {
185    type Output = Path<HCons<Name, Path<RHSParam>>>;
186
187    #[inline(always)]
188    fn add(self, _: Path<RHSParam>) -> Self::Output {
189        Path::new()
190    }
191}
192
193impl<Name, Tail, RHSParam> Add<Path<RHSParam>> for Path<HCons<Name, Path<Tail>>>
194where
195    Path<Tail>: Add<Path<RHSParam>>,
196{
197    #[allow(clippy::type_complexity)]
198    type Output = Path<HCons<Name, <Path<Tail> as Add<Path<RHSParam>>>::Output>>;
199
200    #[inline(always)]
201    fn add(self, _: Path<RHSParam>) -> <Self as Add<Path<RHSParam>>>::Output {
202        Path::new()
203    }
204}