key_paths_core/
lib.rs

1use std::rc::Rc;
2
3// #[derive(Clone)]
4pub enum KeyPaths<Root, Value> {
5    Readable(Rc<dyn for<'a> Fn(&'a Root) -> &'a Value>),
6    Writable(Rc<dyn for<'a> Fn(&'a mut Root) -> &'a mut Value>),
7    FailableReadable(Rc<dyn for<'a> Fn(&'a Root) -> Option<&'a Value>>),
8    FailableWritable(Rc<dyn for<'a> Fn(&'a mut Root) -> Option<&'a mut Value>>),
9    // Prism {
10    //     extract: fn(&Root) -> Option<&Value>,
11    //     embed: fn(Value) -> Root,
12    // },
13    //
14}
15
16impl<Root, Value> KeyPaths<Root, Value> {
17    pub fn readable(get: impl for<'a> Fn(&'a Root) -> &'a Value + 'static) -> Self {
18        Self::Readable(Rc::new(get))
19    }
20
21    pub fn writable(get_mut: impl for<'a> Fn(&'a mut Root) -> &'a mut Value + 'static) -> Self {
22        Self::Writable(Rc::new(get_mut))
23    }
24
25    pub fn failable_readable(
26        get: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static,
27    ) -> Self {
28        Self::FailableReadable(Rc::new(get))
29    }
30
31    pub fn failable_writable(
32        get_mut: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static,
33    ) -> Self {
34        Self::FailableWritable(Rc::new(get_mut))
35    }
36
37    // pub fn prism(extract: fn(&Root) -> Option<&Value>, embed: fn(Value) -> Root) -> Self {
38    //     Self::Prism { extract, embed }
39    // }
40}
41
42impl<Root, Value> KeyPaths<Root, Value> {
43    /// Get an immutable reference if possible
44    pub fn get<'a>(&'a self, root: &'a Root) -> Option<&'a Value> {
45        match self {
46            KeyPaths::Readable(f) => Some(f(root)),
47            KeyPaths::Writable(_) => None, // Writable requires mut
48            KeyPaths::FailableReadable(f) => f(root),
49            KeyPaths::FailableWritable(_) => None, // needs mut
50                                                   // KeyPaths::Prism { extract, .. } => extract(root),
51        }
52    }
53
54    /// Get a mutable reference if possible
55    pub fn get_mut<'a>(&'a self, root: &'a mut Root) -> Option<&'a mut Value> {
56        match self {
57            KeyPaths::Readable(_) => None, // immutable only
58            KeyPaths::Writable(f) => Some(f(root)),
59            KeyPaths::FailableReadable(_) => None, // immutable only
60            KeyPaths::FailableWritable(f) => f(root),
61        }
62    }
63    /// Iter over immutable references if `Value: IntoIterator`
64    pub fn iter<'a, T>(&'a self, root: &'a Root) -> Option<<&'a Value as IntoIterator>::IntoIter>
65    where
66        &'a Value: IntoIterator<Item = &'a T>,
67        T: 'a,
68    {
69        self.get(root).map(|v| v.into_iter())
70    }
71
72    /// Iter over mutable references if `&mut Value: IntoIterator`
73    pub fn iter_mut<'a, T>(
74        &'a self,
75        root: &'a mut Root,
76    ) -> Option<<&'a mut Value as IntoIterator>::IntoIter>
77    where
78        &'a mut Value: IntoIterator<Item = &'a mut T>,
79        T: 'a,
80    {
81        self.get_mut(root).map(|v| v.into_iter())
82    }
83
84    /// Consume root and iterate if `Value: IntoIterator`
85    pub fn into_iter<T>(self, root: Root) -> Option<<Value as IntoIterator>::IntoIter>
86    where
87        Value: IntoIterator<Item = T> + Clone,
88    {
89        match self {
90            KeyPaths::Readable(f) => Some(f(&root).clone().into_iter()), // requires Clone
91            KeyPaths::Writable(_) => None,
92            KeyPaths::FailableReadable(f) => f(&root).map(|v| v.clone().into_iter()),
93            KeyPaths::FailableWritable(_) => None,
94        }
95    }
96}
97
98impl<Root, Mid> KeyPaths<Root, Mid>
99where
100    Root: 'static,
101    Mid: 'static,
102{
103    pub fn compose<Value>(self, mid: KeyPaths<Mid, Value>) -> KeyPaths<Root, Value>
104    where
105        Value: 'static,
106    {
107        use KeyPaths::*;
108
109        match (self, mid) {
110            (Readable(f1), Readable(f2)) => Readable(Rc::new(move |r| f2(f1(r)))),
111
112            (Writable(f1), Writable(f2)) => Writable(Rc::new(move |r| f2(f1(r)))),
113
114            (FailableReadable(f1), Readable(f2)) => {
115                FailableReadable(Rc::new(move |r| f1(r).map(|m| f2(m))))
116            }
117
118            (Readable(f1), FailableReadable(f2)) => FailableReadable(Rc::new(move |r| f2(f1(r)))),
119
120            (FailableReadable(f1), FailableReadable(f2)) => {
121                FailableReadable(Rc::new(move |r| f1(r).and_then(|m| f2(m))))
122            }
123
124            (FailableWritable(f1), Writable(f2)) => {
125                FailableWritable(Rc::new(move |r| f1(r).map(|m| f2(m))))
126            }
127
128            (Writable(f1), FailableWritable(f2)) => FailableWritable(Rc::new(move |r| f2(f1(r)))),
129
130            (FailableWritable(f1), FailableWritable(f2)) => {
131                FailableWritable(Rc::new(move |r| f1(r).and_then(|m| f2(m))))
132            }
133
134            (a, b) => panic!(
135                "Unsupported composition: {:?} then {:?}",
136                kind_name(&a),
137                kind_name(&b)
138            ),
139        }
140    }
141}
142
143fn kind_name<Root, Value>(k: &KeyPaths<Root, Value>) -> &'static str {
144    use KeyPaths::*;
145    match k {
146        Readable(_) => "Readable",
147        Writable(_) => "Writable",
148        FailableReadable(_) => "FailableReadable",
149        FailableWritable(_) => "FailableWritable",
150    }
151}