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