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
201            (ReadableEnum { extract, .. }, Readable(f2)) => {
202                FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m))))
203            }
204
205            (ReadableEnum { extract, .. }, FailableReadable(f2)) => {
206                FailableReadable(Rc::new(move |r| extract(r).and_then(|m| f2(m))))
207            }
208
209            (WritableEnum { extract, .. }, Readable(f2)) => {
210                FailableReadable(Rc::new(move |r| extract(r).map(|m| f2(m))))
211            }
212
213            (WritableEnum { extract, .. }, FailableReadable(f2)) => {
214                FailableReadable(Rc::new(move |r| extract(r).and_then(|m| f2(m))))
215            }
216
217            (WritableEnum { extract_mut, .. }, Writable(f2)) => {
218                FailableWritable(Rc::new(move |r| extract_mut(r).map(|m| f2(m))))
219            }
220
221            // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => {
222            //     // FailableWritable(Rc::new(move |r|
223            //     //     {
224            //     //         // let mut x = extract_mut(r);
225            //     //         // x.as_mut().map(|m| f2(m))
226            //     //         // extract_mut(r).map(|m| f2(m))
227            //     //         // extract_mut(r).and_then(|m| f2(m))
228            //     //         // let x = f2(m);
229            //     //         extract_mut(r).and_then(|a| f2(a))
230            //     //
231            //     //     }
232            //     //
233            //     // ))
234            //     // FailableWritable(Rc::new(move |r| extract_mut(r).and_then(|a| f2(a))))
235            //     // FailableWritable(Rc::new(move |r: &mut Root| {
236            //     //     match extract_mut(r) {
237            //     //         Some(mid) => f2(mid), // mid: &mut Mid -> Option<&mut Value>
238            //     //         None => None,
239            //     //     }
240            //     // }) as Rc<dyn for<'a> Fn(&'a mut Root) -> Option<&'a mut Value>>)
241            //
242            //     FailableWritable(Rc::new(move |r: &mut Root| {
243            //         // First extract the intermediate value using extract_mut
244            //         extract_mut(r).and_then(|intermediate| {
245            //             // Now apply f2 to the intermediate value
246            //             // f2 expects &mut Value but intermediate is &mut Value
247            //             f2(intermediate)
248            //         })
249            //     }))
250            // }
251
252            // (WritableEnum { extract_mut, .. }, FailableWritable(f2)) => {
253            //     FailableWritable(Rc::new(move |r: &mut Root| {
254            //         // Extract the intermediate Mid value
255            //         let mid_ref = extract_mut(r)?;
256            //         // Apply the second function to get the final Value
257            //         f2(mid_ref)
258            //     }))
259            // }
260
261            // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => {
262            //     FailableWritable(Rc::new(move |r: &mut Root| {
263            //         // Extract the intermediate Mid value
264            //         let mid_ref = extract_mut(r)?;
265            //         // Apply the second function to get the final Value
266            //         f2(mid_ref)
267            //     }))
268            // }
269
270            /*            (FailableWritable(f2), WritableEnum { extract_mut, .. }) => {
271                            FailableWritable(Rc::new(move |r: &mut Root| {
272                                extract_mut(r).and_then(|intermediate_mid| f2(intermediate_mid))
273                            }))
274                        }
275            */
276            // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => {
277            //     // Here's the fix: f2 must be a function that operates on a Mid and returns a Value
278            //     // It is already of this type since the 'mid' KeyPaths is KeyPaths<Mid, Value>
279            //     FailableWritable(Rc::new(move |r: &mut Root| {
280            //         extract_mut(r).and_then(|intermediate_mid| f2(intermediate_mid))
281            //     }))
282            // }
283
284            // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => {
285            //     FailableWritable(Rc::new(move |r: &mut Root| -> Option<&mut Value> {
286            //         // Extract the intermediate Mid value
287            //         let mid_ref: &mut Mid = extract_mut(r).unwrap();
288            //         // Apply the second function to get the final Value
289            //         f2(mid_ref)
290            //     }))
291            // }
292            (
293                FailableWritable(f_root_mid),
294                WritableEnum {
295                    extract_mut: exm_mid_val,
296                    ..
297                },
298            ) => {
299                FailableWritable(Rc::new(move |r: &mut Root| {
300                    // First, apply the function that operates on Root.
301                    // This will give us `Option<&mut Mid>`.
302                    let intermediate_mid_ref = f_root_mid(r);
303
304                    // Then, apply the function that operates on Mid.
305                    // This will give us `Option<&mut Value>`.
306                    intermediate_mid_ref.and_then(|intermediate_mid| exm_mid_val(intermediate_mid))
307                }))
308            }
309
310            (WritableEnum { extract_mut, .. }, FailableWritable(f2)) => {
311                FailableWritable(Rc::new(move |r| extract_mut(r).and_then(|m| f2(m))))
312            }
313
314            // New: Writable then WritableEnum => FailableWritable
315            (Writable(f1), WritableEnum { extract_mut, .. }) => {
316                FailableWritable(Rc::new(move |r: &mut Root| {
317                    let mid: &mut Mid = f1(r);
318                    extract_mut(mid)
319                }))
320            }
321
322            (
323                ReadableEnum {
324                    extract: ex1,
325                    embed: em1,
326                },
327                ReadableEnum {
328                    extract: ex2,
329                    embed: em2,
330                },
331            ) => ReadableEnum {
332                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
333                embed: Rc::new(move |v| em1(em2(v))),
334            },
335
336            (
337                WritableEnum {
338                    extract: ex1,
339                    extract_mut,
340                    embed: em1,
341                },
342                ReadableEnum {
343                    extract: ex2,
344                    embed: em2,
345                },
346            ) => ReadableEnum {
347                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
348                embed: Rc::new(move |v| em1(em2(v))),
349            },
350
351            (
352                WritableEnum {
353                    extract: ex1,
354                    extract_mut: exm1,
355                    embed: em1,
356                },
357                WritableEnum {
358                    extract: ex2,
359                    extract_mut: exm2,
360                    embed: em2,
361                },
362            ) => WritableEnum {
363                extract: Rc::new(move |r| ex1(r).and_then(|m| ex2(m))),
364                extract_mut: Rc::new(move |r| exm1(r).and_then(|m| exm2(m))),
365                embed: Rc::new(move |v| em1(em2(v))),
366            },
367
368            (a, b) => panic!(
369                "Unsupported composition: {:?} then {:?}",
370                kind_name(&a),
371                kind_name(&b)
372            ),
373        }
374    }
375}
376
377fn kind_name<Root, Value>(k: &KeyPaths<Root, Value>) -> &'static str {
378    use KeyPaths::*;
379    match k {
380        Readable(_) => "Readable",
381        Writable(_) => "Writable",
382        FailableReadable(_) => "FailableReadable",
383        FailableWritable(_) => "FailableWritable",
384        ReadableEnum { .. } => "ReadableEnum",
385        WritableEnum { .. } => "WritableEnum",
386    }
387}
388
389// ===== Helper macros for enum case keypaths =====
390
391#[macro_export]
392macro_rules! readable_enum_macro {
393    // Unit variant: Enum::Variant
394    ($enum:path, $variant:ident) => {{
395        $crate::KeyPaths::readable_enum(
396            |_| <$enum>::$variant,
397            |e: &$enum| match e { <$enum>::$variant => Some(&()), _ => None },
398        )
399    }};
400    // Single-field tuple variant: Enum::Variant(Inner)
401    ($enum:path, $variant:ident($inner:ty)) => {{
402        $crate::KeyPaths::readable_enum(
403            |v: $inner| <$enum>::$variant(v),
404            |e: &$enum| match e { <$enum>::$variant(v) => Some(v), _ => None },
405        )
406    }};
407}
408
409#[macro_export]
410macro_rules! writable_enum_macro {
411    // Unit variant: Enum::Variant (creates prism to and from ())
412    ($enum:path, $variant:ident) => {{
413        $crate::KeyPaths::writable_enum(
414            |_| <$enum>::$variant,
415            |e: &$enum| match e { <$enum>::$variant => Some(&()), _ => None },
416            |e: &mut $enum| match e { <$enum>::$variant => Some(&mut ()), _ => None },
417        )
418    }};
419    // Single-field tuple variant: Enum::Variant(Inner)
420    ($enum:path, $variant:ident($inner:ty)) => {{
421        $crate::KeyPaths::writable_enum(
422            |v: $inner| <$enum>::$variant(v),
423            |e: &$enum| match e { <$enum>::$variant(v) => Some(v), _ => None },
424            |e: &mut $enum| match e { <$enum>::$variant(v) => Some(v), _ => None },
425        )
426    }};
427}
428