key_paths_core/
lib.rs

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