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
23
24    // New Owned KeyPath types (value semantics)
25    Owned(Rc<dyn Fn(Root) -> Value>),
26    FailableOwned(Rc<dyn Fn(Root) -> Option<Value>>),    
27}
28
29impl<Root, Value> KeyPaths<Root, Value> {
30    #[inline]
31    pub fn readable(get: impl for<'a> Fn(&'a Root) -> &'a Value + 'static) -> Self {
32        Self::Readable(Rc::new(get))
33    }
34
35    #[inline]
36    pub fn writable(get_mut: impl for<'a> Fn(&'a mut Root) -> &'a mut Value + 'static) -> Self {
37        Self::Writable(Rc::new(get_mut))
38    }
39
40    #[inline]
41    pub fn failable_readable(
42        get: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static,
43    ) -> Self {
44        Self::FailableReadable(Rc::new(get))
45    }
46
47    #[inline]
48    pub fn failable_writable(
49        get_mut: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static,
50    ) -> Self {
51        Self::FailableWritable(Rc::new(get_mut))
52    }
53
54    #[inline]
55    pub fn readable_enum(
56        embed: impl Fn(Value) -> Root + 'static,
57        extract: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static,
58    ) -> Self {
59        Self::ReadableEnum {
60            extract: Rc::new(extract),
61            embed: Rc::new(embed),
62        }
63    }
64
65    #[inline]
66    pub fn writable_enum(
67        embed: impl Fn(Value) -> Root + 'static,
68        extract: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static,
69        extract_mut: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static,
70    ) -> Self {
71        Self::WritableEnum {
72            extract: Rc::new(extract),
73            embed: Rc::new(embed),
74            extract_mut: Rc::new(extract_mut),
75        }
76    }
77
78
79    // New Owned KeyPath constructors
80    #[inline]
81    pub fn owned(get: impl Fn(Root) -> Value + 'static) -> Self {
82        Self::Owned(Rc::new(get))
83    }
84
85    #[inline]
86    pub fn failable_owned(get: impl Fn(Root) -> Option<Value> + 'static) -> Self {
87        Self::FailableOwned(Rc::new(get))
88    }
89
90    #[inline]
91    pub fn owned_writable(get: impl Fn(Root) -> Value + 'static) -> Self {
92        Self::Owned(Rc::new(get))
93    }
94    
95    #[inline]
96    pub fn failable_owned_writable(get: impl Fn(Root) -> Option<Value> + 'static) -> Self {
97        Self::FailableOwned(Rc::new(get))
98    }
99}
100
101impl<Root, Value> KeyPaths<Root, Value> {
102    /// Get an immutable reference if possible
103    #[inline]
104    pub fn get<'a>(&'a self, root: &'a Root) -> Option<&'a Value> {
105        match self {
106            KeyPaths::Readable(f) => Some(f(root)),
107            KeyPaths::Writable(_) => None, // Writable requires mut
108            KeyPaths::FailableReadable(f) => f(root),
109            KeyPaths::FailableWritable(_) => None, // needs mut
110            KeyPaths::ReadableEnum { extract, .. } => extract(root),
111            KeyPaths::WritableEnum { extract, .. } => extract(root),
112            // New owned keypath types (don't work with references)
113            KeyPaths::Owned(_) => None, // Owned keypaths don't work with references
114            KeyPaths::FailableOwned(_) => None, // Owned keypaths don't work with references
115        }
116    }
117
118    /// Get a mutable reference if possible
119    #[inline]
120    pub fn get_mut<'a>(&'a self, root: &'a mut Root) -> Option<&'a mut Value> {
121        match self {
122            KeyPaths::Readable(_) => None, // immutable only
123            KeyPaths::Writable(f) => Some(f(root)),
124            KeyPaths::FailableReadable(_) => None, // immutable only
125            KeyPaths::FailableWritable(f) => f(root),
126            KeyPaths::ReadableEnum { .. } => None, // immutable only
127            KeyPaths::WritableEnum { extract_mut, .. } => extract_mut(root),
128            // New owned keypath types (don't work with references)
129            KeyPaths::Owned(_) => None, // Owned keypaths don't work with references
130            KeyPaths::FailableOwned(_) => None, // Owned keypaths don't work with references
131        }
132    }
133
134    pub fn embed(&self, value: Value) -> Option<Root>
135    where
136        Value: Clone,
137    {
138        match self {
139            KeyPaths::ReadableEnum { embed, .. } => Some(embed(value)),
140            _ => None,
141        }
142    }
143
144    pub fn embed_mut(&self, value: Value) -> Option<Root>
145    where
146        Value: Clone,
147    {
148        match self {
149            KeyPaths::WritableEnum { embed, .. } => Some(embed(value)),
150            _ => None,
151        }
152    }
153
154
155    // ===== Owned KeyPath Accessor Methods =====
156
157    /// Get an owned value (primary method for owned keypaths)
158    #[inline]
159    pub fn get_owned(self, root: Root) -> Value {
160        match self {
161            KeyPaths::Owned(f) => f(root),
162            _ => panic!("get_owned only works with owned keypaths"),
163        }
164    }
165
166    /// Get an owned value with failable access
167    #[inline]
168    pub fn get_failable_owned(self, root: Root) -> Option<Value> {
169        match self {
170            KeyPaths::FailableOwned(f) => f(root),
171            _ => panic!("get_failable_owned only works with failable owned keypaths"),
172        }
173    }
174
175    /// Iter over immutable references if `Value: IntoIterator`
176    pub fn iter<'a, T>(&'a self, root: &'a Root) -> Option<<&'a Value as IntoIterator>::IntoIter>
177    where
178        &'a Value: IntoIterator<Item = &'a T>,
179        T: 'a,
180    {
181        self.get(root).map(|v| v.into_iter())
182    }
183
184    /// Iter over mutable references if `&mut Value: IntoIterator`
185    pub fn iter_mut<'a, T>(
186        &'a self,
187        root: &'a mut Root,
188    ) -> Option<<&'a mut Value as IntoIterator>::IntoIter>
189    where
190        &'a mut Value: IntoIterator<Item = &'a mut T>,
191        T: 'a,
192    {
193        self.get_mut(root).map(|v| v.into_iter())
194    }
195
196    /// Consume root and iterate if `Value: IntoIterator`
197    #[inline]
198    pub fn into_iter<T>(self, root: Root) -> Option<<Value as IntoIterator>::IntoIter>
199    where
200        Value: IntoIterator<Item = T> + Clone,
201    {
202        match self {
203            KeyPaths::Readable(f) => Some(f(&root).clone().into_iter()), // requires Clone
204            KeyPaths::Writable(_) => None,
205            KeyPaths::FailableReadable(f) => f(&root).map(|v| v.clone().into_iter()),
206            KeyPaths::FailableWritable(_) => None,
207            KeyPaths::ReadableEnum { extract, .. } => extract(&root).map(|v| v.clone().into_iter()),
208            KeyPaths::WritableEnum { extract, .. } => extract(&root).map(|v| v.clone().into_iter()),
209            // New owned keypath types
210            KeyPaths::Owned(f) => Some(f(root).into_iter()),
211            KeyPaths::FailableOwned(f) => f(root).map(|v| v.into_iter()),
212        }
213    }
214}
215
216impl<Root, Mid> KeyPaths<Root, Mid>
217where
218    Root: 'static,
219    Mid: 'static,
220{
221    /// Alias for `compose` for ergonomic chaining.
222    #[inline]
223    pub fn then<Value>(self, mid: KeyPaths<Mid, Value>) -> KeyPaths<Root, Value>
224    where
225        Value: 'static,
226    {
227        self.compose(mid)
228    }
229
230    pub fn compose<Value>(self, mid: KeyPaths<Mid, Value>) -> KeyPaths<Root, Value>
231    where
232        Value: 'static,
233    {
234        use KeyPaths::*;
235
236        match (self, mid) {
237            (Readable(f1), Readable(f2)) => Readable(Rc::new(move |r| f2(f1(r)))),
238
239            (Writable(f1), Writable(f2)) => Writable(Rc::new(move |r| f2(f1(r)))),
240
241            (FailableReadable(f1), Readable(f2)) => {
242                FailableReadable(Rc::new(move |r| f1(r).map(|m| f2(m))))
243            }
244
245            (Readable(f1), FailableReadable(f2)) => FailableReadable(Rc::new(move |r| f2(f1(r)))),
246
247            (FailableReadable(f1), FailableReadable(f2)) => {
248                FailableReadable(Rc::new(move |r| f1(r).and_then(|m| f2(m))))
249            }
250
251            (FailableWritable(f1), Writable(f2)) => {
252                FailableWritable(Rc::new(move |r| f1(r).map(|m| f2(m))))
253            }
254
255            (Writable(f1), FailableWritable(f2)) => FailableWritable(Rc::new(move |r| f2(f1(r)))),
256
257            (FailableWritable(f1), FailableWritable(f2)) => {
258                FailableWritable(Rc::new(move |r| f1(r).and_then(|m| f2(m))))
259            }
260            (FailableReadable(f1), ReadableEnum { extract, .. }) => {
261                FailableReadable(Rc::new(move |r| f1(r).and_then(|m| extract(m))))
262            }
263            // (ReadableEnum { extract, .. }, FailableReadable(f2)) => {
264            //     FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m).unwrap())))
265            // }
266            (ReadableEnum { extract, .. }, Readable(f2)) => {
267                FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m))))
268            }
269
270            (ReadableEnum { extract, .. }, FailableReadable(f2)) => {
271                FailableReadable(Rc::new(move |r| extract(r).and_then(|m| f2(m))))
272            }
273
274            (WritableEnum { extract, .. }, Readable(f2)) => {
275                FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m))))
276            }
277
278            (WritableEnum { extract, .. }, FailableReadable(f2)) => {
279                FailableReadable(Rc::new(move |r| extract(r).and_then(|m| f2(m))))
280            }
281
282            (WritableEnum { extract_mut, .. }, Writable(f2)) => {
283                FailableWritable(Rc::new(move |r| extract_mut(r).map(|m| f2(m))))
284            }
285
286            (
287                FailableWritable(f_root_mid),
288                WritableEnum {
289                    extract_mut: exm_mid_val,
290                    ..
291                },
292            ) => {
293                FailableWritable(Rc::new(move |r: &mut Root| {
294                    // First, apply the function that operates on Root.
295                    // This will give us `Option<&mut Mid>`.
296                    let intermediate_mid_ref = f_root_mid(r);
297
298                    // Then, apply the function that operates on Mid.
299                    // This will give us `Option<&mut Value>`.
300                    intermediate_mid_ref.and_then(|intermediate_mid| exm_mid_val(intermediate_mid))
301                }))
302            }
303
304            (WritableEnum { extract_mut, .. }, FailableWritable(f2)) => {
305                FailableWritable(Rc::new(move |r| extract_mut(r).and_then(|m| f2(m))))
306            }
307
308            // New: Writable then WritableEnum => FailableWritable
309            (Writable(f1), WritableEnum { extract_mut, .. }) => {
310                FailableWritable(Rc::new(move |r: &mut Root| {
311                    let mid: &mut Mid = f1(r);
312                    extract_mut(mid)
313                }))
314            }
315
316            (
317                ReadableEnum {
318                    extract: ex1,
319                    embed: em1,
320                },
321                ReadableEnum {
322                    extract: ex2,
323                    embed: em2,
324                },
325            ) => ReadableEnum {
326                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
327                embed: Rc::new(move |v| em1(em2(v))),
328            },
329
330            (
331                WritableEnum {
332                    extract: ex1,
333                    extract_mut: _,
334                    embed: em1,
335                },
336                ReadableEnum {
337                    extract: ex2,
338                    embed: em2,
339                },
340            ) => ReadableEnum {
341                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
342                embed: Rc::new(move |v| em1(em2(v))),
343            },
344
345            (
346                WritableEnum {
347                    extract: ex1,
348                    extract_mut: exm1,
349                    embed: em1,
350                },
351                WritableEnum {
352                    extract: ex2,
353                    extract_mut: exm2,
354                    embed: em2,
355                },
356            ) => WritableEnum {
357                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
358                extract_mut: Rc::new(move |r| exm1(r).and_then(|m| exm2(m))),
359                embed: Rc::new(move |v| em1(em2(v))),
360            },
361
362
363            // New owned keypath compositions
364            (Owned(f1), Owned(f2)) => {
365                Owned(Rc::new(move |r| f2(f1(r))))
366            }
367            (FailableOwned(f1), Owned(f2)) => {
368                FailableOwned(Rc::new(move |r| f1(r).map(|m| f2(m))))
369            }
370            (Owned(f1), FailableOwned(f2)) => {
371                FailableOwned(Rc::new(move |r| f2(f1(r))))
372            }
373            (FailableOwned(f1), FailableOwned(f2)) => {
374                FailableOwned(Rc::new(move |r| f1(r).and_then(|m| f2(m))))
375            }
376
377            // Cross-composition between owned and regular keypaths
378            // Note: These compositions require Clone bounds which may not always be available
379            // For now, we'll skip these complex compositions
380
381            (a, b) => panic!(
382                "Unsupported composition: {:?} then {:?}",
383                kind_name(&a),
384                kind_name(&b)
385            ),
386        }
387    }
388
389    /// Get the kind name of this keypath
390    #[inline]
391    pub fn kind_name(&self) -> &'static str {
392        kind_name(self)
393    }
394}
395
396fn kind_name<Root, Value>(k: &KeyPaths<Root, Value>) -> &'static str {
397    use KeyPaths::*;
398    match k {
399        Readable(_) => "Readable",
400        Writable(_) => "Writable",
401        FailableReadable(_) => "FailableReadable",
402        FailableWritable(_) => "FailableWritable",
403        ReadableEnum { .. } => "ReadableEnum",
404        WritableEnum { .. } => "WritableEnum",
405        // New owned keypath types
406        Owned(_) => "Owned",
407        FailableOwned(_) => "FailableOwned",
408    }
409}
410
411// ===== Helper functions for creating reusable getter functions =====
412// Note: These helper functions have lifetime constraints that make them
413// difficult to implement in Rust's current type system. The keypath
414// instances themselves can be used directly for access.
415
416// ===== Global compose function =====
417
418/// Global compose function that combines two compatible key paths
419pub fn compose<Root, Mid, Value>(
420    kp1: KeyPaths<Root, Mid>,
421    kp2: KeyPaths<Mid, Value>,
422) -> KeyPaths<Root, Value>
423where
424    Root: 'static,
425    Mid: 'static,
426    Value: 'static,
427{
428    kp1.compose(kp2)
429}
430
431// ===== Helper macros for enum case keypaths =====
432
433#[macro_export]
434macro_rules! readable_enum_macro {
435    // Unit variant: Enum::Variant
436    ($enum:path, $variant:ident) => {{
437        $crate::KeyPaths::readable_enum(
438            |_| <$enum>::$variant,
439            |e: &$enum| match e {
440                <$enum>::$variant => Some(&()),
441                _ => None,
442            },
443        )
444    }};
445    // Single-field tuple variant: Enum::Variant(Inner)
446    ($enum:path, $variant:ident($inner:ty)) => {{
447        $crate::KeyPaths::readable_enum(
448            |v: $inner| <$enum>::$variant(v),
449            |e: &$enum| match e {
450                <$enum>::$variant(v) => Some(v),
451                _ => None,
452            },
453        )
454    }};
455}
456
457#[macro_export]
458macro_rules! writable_enum_macro {
459    // Unit variant: Enum::Variant (creates prism to and from ())
460    ($enum:path, $variant:ident) => {{
461        $crate::KeyPaths::writable_enum(
462            |_| <$enum>::$variant,
463            |e: &$enum| match e {
464                <$enum>::$variant => Some(&()),
465                _ => None,
466            },
467            |e: &mut $enum| match e {
468                <$enum>::$variant => Some(&mut ()),
469                _ => None,
470            },
471        )
472    }};
473    // Single-field tuple variant: Enum::Variant(Inner)
474    ($enum:path, $variant:ident($inner:ty)) => {{
475        $crate::KeyPaths::writable_enum(
476            |v: $inner| <$enum>::$variant(v),
477            |e: &$enum| match e {
478                <$enum>::$variant(v) => Some(v),
479                _ => None,
480            },
481            |e: &mut $enum| match e {
482                <$enum>::$variant(v) => Some(v),
483                _ => None,
484            },
485        )
486    }};
487}