Skip to main content

haste_pointer/
lib.rs

1use haste_reflect::MetaValue;
2use std::{fmt::Display, sync::Arc};
3
4mod escape;
5
6#[cfg(test)]
7mod proptests;
8
9#[derive(Debug, Clone)]
10pub struct Path(String);
11
12impl Default for Path {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17
18impl Display for Path {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        write!(f, "{}", self.0)
21    }
22}
23
24impl Path {
25    #[must_use]
26    pub fn new() -> Self {
27        Self(String::new())
28    }
29    #[must_use]
30    pub fn descend(&self, field: &str) -> Self {
31        Self(format!("{}/{}", self.0, escape::escape_field(field)))
32    }
33    #[must_use]
34    pub fn ascend(&self) -> Option<(Self, Key)> {
35        if self.0.is_empty() {
36            return None;
37        }
38
39        // Find the last '/' separator from the end of the string
40        match self.0.rfind('/') {
41            // Case 1: Separator found (e.g., "a/b/c" -> parent: "a/b", field: "c")
42            Some(idx) => {
43                let parent_path = &self.0[..idx];
44                let field = &self.0[idx + 1..];
45                Some((
46                    Path(parent_path.to_string()),
47                    Key::parse(&escape::unescape_field(field)),
48                ))
49            }
50            // Case 2: No separator found, the string is a single field (e.g., "a" -> parent: "", field: "a")
51            None => Some((
52                Path(String::new()),
53                Key::parse(&escape::unescape_field(&self.0)),
54            )),
55        }
56    }
57
58    pub fn get<'a>(&self, value: &'a dyn MetaValue) -> Option<&'a dyn MetaValue> {
59        let mut current = value;
60        // Skip the first empty part from the leading '/'
61        for part in self.0.split('/').skip(1) {
62            let k = Key::parse(&escape::unescape_field(part));
63
64            match k {
65                Key::Field(field) => {
66                    current = current.get_field(&field)?;
67                }
68                Key::Index(index) => {
69                    current = current.get_index(index)?;
70                }
71            }
72        }
73
74        Some(current)
75    }
76
77    pub fn get_typed<'a, Type: MetaValue>(&self, value: &'a dyn MetaValue) -> Option<&'a Type> {
78        let current = self.get(value)?;
79        current.as_any().downcast_ref::<Type>()
80    }
81}
82
83#[derive(Debug)]
84pub enum Key {
85    Field(String),
86    Index(usize),
87}
88
89impl Key {
90    #[must_use]
91    pub fn parse(field: &str) -> Self {
92        if let Ok(index) = field.parse::<usize>() {
93            Key::Index(index)
94        } else {
95            Key::Field(field.to_string())
96        }
97    }
98}
99
100#[derive(Clone)]
101struct ChildPointer<U>(*const U);
102
103unsafe impl<U> Send for ChildPointer<U> {}
104unsafe impl<U> Sync for ChildPointer<U> {}
105
106#[derive(Clone)]
107pub struct TypedPointer<T: MetaValue, U: MetaValue> {
108    root: Arc<T>,
109    value: ChildPointer<U>,
110    path: Path,
111}
112
113impl<Root: MetaValue, U: MetaValue> TypedPointer<Root, U> {
114    pub fn new(value: Arc<Root>) -> TypedPointer<Root, Root> {
115        TypedPointer {
116            value: ChildPointer(&raw const *value.as_ref()),
117            root: value,
118            path: Path::new(),
119        }
120    }
121
122    #[must_use]
123    pub fn root(&self) -> TypedPointer<Root, Root> {
124        TypedPointer {
125            value: ChildPointer(&raw const *self.root.as_ref()),
126            root: self.root.clone(),
127            path: Path::new(),
128        }
129    }
130
131    #[must_use]
132    pub fn path(&self) -> &str {
133        self.path.0.as_str()
134    }
135
136    #[must_use]
137    pub fn value(&self) -> Option<&U> {
138        unsafe { (*self.value.0).as_any().downcast_ref::<U>() }
139    }
140
141    #[must_use]
142    pub fn descend<Child: MetaValue>(&self, field: &Key) -> Option<TypedPointer<Root, Child>> {
143        match field {
144            Key::Field(field) => self.value().and_then(|v| {
145                v.get_field(field)
146                    .and_then(|v| v.as_any().downcast_ref::<Child>())
147                    .map(|child| TypedPointer {
148                        root: self.root.clone(),
149                        value: ChildPointer(&raw const *child),
150                        path: self.path.descend(field),
151                    })
152            }),
153            Key::Index(index) => self.value().and_then(|v| {
154                v.get_index(*index)
155                    .and_then(|v| v.as_any().downcast_ref::<Child>())
156                    .map(|child| TypedPointer {
157                        root: self.root.clone(),
158                        value: ChildPointer(&raw const *child),
159                        path: self.path.descend(&index.to_string()),
160                    })
161            }),
162        }
163    }
164
165    #[must_use]
166    pub fn ascend(&self) -> Option<(Path, Key)> {
167        self.path.ascend()
168    }
169}
170
171#[cfg(test)]
172mod test {
173    use super::*;
174    use haste_fhir_model::r4::generated::{
175        resources::Patient, types::FHIRString, types::HumanName,
176    };
177
178    #[test]
179    fn test_pointer_descend() {
180        let patient = Arc::new(Patient {
181            id: Some("patient-1".to_string()),
182            name: Some(vec![HumanName {
183                family: Some(Box::new(FHIRString {
184                    value: Some("Doe".to_string()),
185                    ..Default::default()
186                })),
187                ..Default::default()
188            }]),
189            ..Default::default()
190        });
191
192        let pointer = TypedPointer::<Patient, Patient>::new(patient);
193        let pointer = pointer
194            .descend::<Vec<HumanName>>(&Key::Field("name".to_string()))
195            .unwrap();
196        assert_eq!(pointer.path(), "/name");
197        let pointer = pointer.descend::<HumanName>(&Key::Index(0)).unwrap();
198        assert_eq!(pointer.path(), "/name/0");
199        let pointer = pointer
200            .descend::<Box<FHIRString>>(&Key::Field("family".to_string()))
201            .unwrap();
202        let pointer = pointer
203            .descend::<String>(&Key::Field("value".to_string()))
204            .unwrap();
205
206        assert_eq!(pointer.path(), "/name/0/family/value");
207        assert_eq!(pointer.value(), Some(&"Doe".to_string()));
208    }
209
210    #[test]
211    fn test_path() {
212        let patient = Arc::new(Patient {
213            id: Some("patient-1".to_string()),
214            name: Some(vec![HumanName {
215                family: Some(Box::new(FHIRString {
216                    value: Some("Doe".to_string()),
217                    ..Default::default()
218                })),
219                ..Default::default()
220            }]),
221            ..Default::default()
222        });
223
224        let path = Path::new()
225            .descend("name")
226            .descend("0")
227            .descend("family")
228            .descend("value");
229
230        assert_eq!(path.0, "/name/0/family/value");
231        let k = path.get_typed::<String>(patient.as_ref());
232
233        assert_eq!(k, Some(&"Doe".to_string()));
234    }
235}