key_paths_core/
lib.rs

1use std::rc::Rc;
2
3#[derive(Clone)]
4/// Go to examples section to see the implementations
5///
6pub enum KeyPaths<Root, Value> {
7    Readable(Rc<dyn for<'a> Fn(&'a Root) -> &'a Value>),
8    ReadableEnum {
9        extract: Rc<dyn for<'a> Fn(&'a Root) -> Option<&'a Value>>,
10        embed: Rc<dyn Fn(Value) -> Root>,
11    },
12    FailableReadable(Rc<dyn for<'a> Fn(&'a Root) -> Option<&'a Value>>),
13
14    Writable(Rc<dyn for<'a> Fn(&'a mut Root) -> &'a mut Value>),
15    FailableWritable(Rc<dyn for<'a> Fn(&'a mut Root) -> Option<&'a mut Value>>),
16    WritableEnum {
17        extract: Rc<dyn for<'a> Fn(&'a Root) -> Option<&'a Value>>,
18        extract_mut: Rc<dyn for<'a> Fn(&'a mut Root) -> Option<&'a mut Value>>,
19        embed: Rc<dyn Fn(Value) -> Root>,
20    },
21}
22
23impl<Root, Value> KeyPaths<Root, Value> {
24    #[inline]
25    pub fn readable(get: impl for<'a> Fn(&'a Root) -> &'a Value + 'static) -> Self {
26        Self::Readable(Rc::new(get))
27    }
28
29    #[inline]
30    pub fn writable(get_mut: impl for<'a> Fn(&'a mut Root) -> &'a mut Value + 'static) -> Self {
31        Self::Writable(Rc::new(get_mut))
32    }
33
34    #[inline]
35    pub fn failable_readable(
36        get: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static,
37    ) -> Self {
38        Self::FailableReadable(Rc::new(get))
39    }
40
41    #[inline]
42    pub fn failable_writable(
43        get_mut: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static,
44    ) -> Self {
45        Self::FailableWritable(Rc::new(get_mut))
46    }
47
48    #[inline]
49    pub fn readable_enum(
50        embed: impl Fn(Value) -> Root + 'static,
51        extract: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static,
52    ) -> Self {
53        Self::ReadableEnum {
54            extract: Rc::new(extract),
55            embed: Rc::new(embed),
56        }
57    }
58
59    #[inline]
60    pub fn writable_enum(
61        embed: impl Fn(Value) -> Root + 'static,
62        extract: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static,
63        extract_mut: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static,
64    ) -> Self {
65        Self::WritableEnum {
66            extract: Rc::new(extract),
67            embed: Rc::new(embed),
68            extract_mut: Rc::new(extract_mut),
69        }
70    }
71}
72
73impl<Root, Value> KeyPaths<Root, Value> {
74    /// Get an immutable reference if possible
75    #[inline]
76    pub fn get<'a>(&'a self, root: &'a Root) -> Option<&'a Value> {
77        match self {
78            KeyPaths::Readable(f) => Some(f(root)),
79            KeyPaths::Writable(_) => None, // Writable requires mut
80            KeyPaths::FailableReadable(f) => f(root),
81            KeyPaths::FailableWritable(_) => None, // needs mut
82            KeyPaths::ReadableEnum { extract, .. } => extract(root),
83            KeyPaths::WritableEnum { extract, .. } => extract(root),
84        }
85    }
86
87    /// Get a mutable reference if possible
88    #[inline]
89    pub fn get_mut<'a>(&'a self, root: &'a mut Root) -> Option<&'a mut Value> {
90        match self {
91            KeyPaths::Readable(_) => None, // immutable only
92            KeyPaths::Writable(f) => Some(f(root)),
93            KeyPaths::FailableReadable(_) => None, // immutable only
94            KeyPaths::FailableWritable(f) => f(root),
95            KeyPaths::WritableEnum { extract_mut, .. } => extract_mut(root),
96            _ => None,
97        }
98    }
99
100    pub fn embed(&self, value: Value) -> Option<Root>
101    where
102        Value: Clone,
103    {
104        match self {
105            KeyPaths::ReadableEnum { embed, .. } => Some(embed(value)),
106            _ => None,
107        }
108    }
109
110    pub fn embed_mut(&self, value: Value) -> Option<Root>
111    where
112        Value: Clone,
113    {
114        match self {
115            KeyPaths::WritableEnum { embed, .. } => Some(embed(value)),
116            _ => None,
117        }
118    }
119
120    /// Iter over immutable references if `Value: IntoIterator`
121    pub fn iter<'a, T>(&'a self, root: &'a Root) -> Option<<&'a Value as IntoIterator>::IntoIter>
122    where
123        &'a Value: IntoIterator<Item = &'a T>,
124        T: 'a,
125    {
126        self.get(root).map(|v| v.into_iter())
127    }
128
129    /// Iter over mutable references if `&mut Value: IntoIterator`
130    pub fn iter_mut<'a, T>(
131        &'a self,
132        root: &'a mut Root,
133    ) -> Option<<&'a mut Value as IntoIterator>::IntoIter>
134    where
135        &'a mut Value: IntoIterator<Item = &'a mut T>,
136        T: 'a,
137    {
138        self.get_mut(root).map(|v| v.into_iter())
139    }
140
141    /// Consume root and iterate if `Value: IntoIterator`
142    #[inline]
143    pub fn into_iter<T>(self, root: Root) -> Option<<Value as IntoIterator>::IntoIter>
144    where
145        Value: IntoIterator<Item = T> + Clone,
146    {
147        match self {
148            KeyPaths::Readable(f) => Some(f(&root).clone().into_iter()), // requires Clone
149            KeyPaths::Writable(_) => None,
150            KeyPaths::FailableReadable(f) => f(&root).map(|v| v.clone().into_iter()),
151            KeyPaths::FailableWritable(_) => None,
152            KeyPaths::ReadableEnum { extract, .. } => extract(&root).map(|v| v.clone().into_iter()),
153            KeyPaths::WritableEnum { extract, .. } => extract(&root).map(|v| v.clone().into_iter()),
154        }
155    }
156}
157
158impl<Root, Mid> KeyPaths<Root, Mid>
159where
160    Root: 'static,
161    Mid: 'static,
162{
163    /// Alias for `compose` for ergonomic chaining.
164    #[inline]
165    pub fn then<Value>(self, mid: KeyPaths<Mid, Value>) -> KeyPaths<Root, Value>
166    where
167        Value: 'static,
168    {
169        self.compose(mid)
170    }
171
172    pub fn compose<Value>(self, mid: KeyPaths<Mid, Value>) -> KeyPaths<Root, Value>
173    where
174        Value: 'static,
175    {
176        use KeyPaths::*;
177
178        match (self, mid) {
179            (Readable(f1), Readable(f2)) => Readable(Rc::new(move |r| f2(f1(r)))),
180
181            (Writable(f1), Writable(f2)) => Writable(Rc::new(move |r| f2(f1(r)))),
182
183            (FailableReadable(f1), Readable(f2)) => {
184                FailableReadable(Rc::new(move |r| f1(r).map(|m| f2(m))))
185            }
186
187            (Readable(f1), FailableReadable(f2)) => FailableReadable(Rc::new(move |r| f2(f1(r)))),
188
189            (FailableReadable(f1), FailableReadable(f2)) => {
190                FailableReadable(Rc::new(move |r| f1(r).and_then(|m| f2(m))))
191            }
192
193            (FailableWritable(f1), Writable(f2)) => {
194                FailableWritable(Rc::new(move |r| f1(r).map(|m| f2(m))))
195            }
196
197            (Writable(f1), FailableWritable(f2)) => FailableWritable(Rc::new(move |r| f2(f1(r)))),
198
199            (FailableWritable(f1), FailableWritable(f2)) => {
200                FailableWritable(Rc::new(move |r| f1(r).and_then(|m| f2(m))))
201            }
202            (FailableReadable(f1), ReadableEnum { extract, .. }) => {
203                FailableReadable(Rc::new(move |r| f1(r).and_then(|m| extract(m))))
204            }
205            // (ReadableEnum { extract, .. }, FailableReadable(f2)) => {
206            //     FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m).unwrap())))
207            // }
208            (ReadableEnum { extract, .. }, Readable(f2)) => {
209                FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m))))
210            }
211
212            (ReadableEnum { extract, .. }, FailableReadable(f2)) => {
213                FailableReadable(Rc::new(move |r| extract(r).and_then(|m| f2(m))))
214            }
215
216            (WritableEnum { extract, .. }, Readable(f2)) => {
217                FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m))))
218            }
219
220            (WritableEnum { extract, .. }, FailableReadable(f2)) => {
221                FailableReadable(Rc::new(move |r| extract(r).and_then(|m| f2(m))))
222            }
223
224            (WritableEnum { extract_mut, .. }, Writable(f2)) => {
225                FailableWritable(Rc::new(move |r| extract_mut(r).map(|m| f2(m))))
226            }
227
228            (
229                FailableWritable(f_root_mid),
230                WritableEnum {
231                    extract_mut: exm_mid_val,
232                    ..
233                },
234            ) => {
235                FailableWritable(Rc::new(move |r: &mut Root| {
236                    // First, apply the function that operates on Root.
237                    // This will give us `Option<&mut Mid>`.
238                    let intermediate_mid_ref = f_root_mid(r);
239
240                    // Then, apply the function that operates on Mid.
241                    // This will give us `Option<&mut Value>`.
242                    intermediate_mid_ref.and_then(|intermediate_mid| exm_mid_val(intermediate_mid))
243                }))
244            }
245
246            (WritableEnum { extract_mut, .. }, FailableWritable(f2)) => {
247                FailableWritable(Rc::new(move |r| extract_mut(r).and_then(|m| f2(m))))
248            }
249
250            // New: Writable then WritableEnum => FailableWritable
251            (Writable(f1), WritableEnum { extract_mut, .. }) => {
252                FailableWritable(Rc::new(move |r: &mut Root| {
253                    let mid: &mut Mid = f1(r);
254                    extract_mut(mid)
255                }))
256            }
257
258            (
259                ReadableEnum {
260                    extract: ex1,
261                    embed: em1,
262                },
263                ReadableEnum {
264                    extract: ex2,
265                    embed: em2,
266                },
267            ) => ReadableEnum {
268                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
269                embed: Rc::new(move |v| em1(em2(v))),
270            },
271
272            (
273                WritableEnum {
274                    extract: ex1,
275                    extract_mut,
276                    embed: em1,
277                },
278                ReadableEnum {
279                    extract: ex2,
280                    embed: em2,
281                },
282            ) => ReadableEnum {
283                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
284                embed: Rc::new(move |v| em1(em2(v))),
285            },
286
287            (
288                WritableEnum {
289                    extract: ex1,
290                    extract_mut: exm1,
291                    embed: em1,
292                },
293                WritableEnum {
294                    extract: ex2,
295                    extract_mut: exm2,
296                    embed: em2,
297                },
298            ) => WritableEnum {
299                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
300                extract_mut: Rc::new(move |r| exm1(r).and_then(|m| exm2(m))),
301                embed: Rc::new(move |v| em1(em2(v))),
302            },
303
304            (a, b) => panic!(
305                "Unsupported composition: {:?} then {:?}",
306                kind_name(&a),
307                kind_name(&b)
308            ),
309        }
310    }
311}
312
313fn kind_name<Root, Value>(k: &KeyPaths<Root, Value>) -> &'static str {
314    use KeyPaths::*;
315    match k {
316        Readable(_) => "Readable",
317        Writable(_) => "Writable",
318        FailableReadable(_) => "FailableReadable",
319        FailableWritable(_) => "FailableWritable",
320        ReadableEnum { .. } => "ReadableEnum",
321        WritableEnum { .. } => "WritableEnum",
322    }
323}
324
325// ===== Helper macros for enum case keypaths =====
326
327#[macro_export]
328macro_rules! readable_enum_macro {
329    // Unit variant: Enum::Variant
330    ($enum:path, $variant:ident) => {{
331        $crate::KeyPaths::readable_enum(
332            |_| <$enum>::$variant,
333            |e: &$enum| match e {
334                <$enum>::$variant => Some(&()),
335                _ => None,
336            },
337        )
338    }};
339    // Single-field tuple variant: Enum::Variant(Inner)
340    ($enum:path, $variant:ident($inner:ty)) => {{
341        $crate::KeyPaths::readable_enum(
342            |v: $inner| <$enum>::$variant(v),
343            |e: &$enum| match e {
344                <$enum>::$variant(v) => Some(v),
345                _ => None,
346            },
347        )
348    }};
349}
350
351#[macro_export]
352macro_rules! writable_enum_macro {
353    // Unit variant: Enum::Variant (creates prism to and from ())
354    ($enum:path, $variant:ident) => {{
355        $crate::KeyPaths::writable_enum(
356            |_| <$enum>::$variant,
357            |e: &$enum| match e {
358                <$enum>::$variant => Some(&()),
359                _ => None,
360            },
361            |e: &mut $enum| match e {
362                <$enum>::$variant => Some(&mut ()),
363                _ => None,
364            },
365        )
366    }};
367    // Single-field tuple variant: Enum::Variant(Inner)
368    ($enum:path, $variant:ident($inner:ty)) => {{
369        $crate::KeyPaths::writable_enum(
370            |v: $inner| <$enum>::$variant(v),
371            |e: &$enum| match e {
372                <$enum>::$variant(v) => Some(v),
373                _ => None,
374            },
375            |e: &mut $enum| match e {
376                <$enum>::$variant(v) => Some(v),
377                _ => None,
378            },
379        )
380    }};
381}