chemfiles/
property.rs

1// Chemfiles, a modern library for chemistry file reading and writing
2// Copyright (C) 2015-2018 Guillaume Fraux -- BSD licensed
3use chemfiles_sys as ffi;
4
5use crate::errors::{check, check_not_null, check_success, Error};
6use crate::strings;
7
8/// A thin wrapper around `ffi::CHFL_PROPERTY`
9#[derive(Debug)]
10pub(crate) struct RawProperty {
11    handle: *mut ffi::CHFL_PROPERTY,
12}
13
14impl RawProperty {
15    /// Create a `RawProperty` from a C pointer.
16    ///
17    /// This function is unsafe because no validity check is made on the pointer.
18    pub unsafe fn from_ptr(ptr: *mut ffi::CHFL_PROPERTY) -> RawProperty {
19        check_not_null(ptr);
20        RawProperty { handle: ptr }
21    }
22
23    /// Get the underlying C pointer as a const pointer.
24    pub fn as_ptr(&self) -> *const ffi::CHFL_PROPERTY {
25        self.handle
26    }
27
28    fn double(value: f64) -> RawProperty {
29        unsafe {
30            let handle = ffi::chfl_property_double(value);
31            RawProperty::from_ptr(handle)
32        }
33    }
34
35    fn bool(value: bool) -> RawProperty {
36        unsafe {
37            let handle = ffi::chfl_property_bool(u8::from(value));
38            RawProperty::from_ptr(handle)
39        }
40    }
41
42    fn vector3d(value: [f64; 3]) -> RawProperty {
43        unsafe {
44            let handle = ffi::chfl_property_vector3d(value.as_ptr());
45            RawProperty::from_ptr(handle)
46        }
47    }
48
49    fn string(value: &str) -> RawProperty {
50        let buffer = strings::to_c(value);
51        unsafe {
52            let handle = ffi::chfl_property_string(buffer.as_ptr());
53            RawProperty::from_ptr(handle)
54        }
55    }
56
57    fn get_kind(&self) -> ffi::chfl_property_kind {
58        let mut kind = ffi::chfl_property_kind::CHFL_PROPERTY_BOOL;
59        unsafe {
60            check_success(ffi::chfl_property_get_kind(self.as_ptr(), &mut kind));
61        }
62        return kind;
63    }
64
65    fn get_bool(&self) -> Result<bool, Error> {
66        let mut value = 0;
67        unsafe {
68            check(ffi::chfl_property_get_bool(self.as_ptr(), &mut value))?;
69        }
70        return Ok(value != 0);
71    }
72
73    fn get_double(&self) -> Result<f64, Error> {
74        let mut value = 0.0;
75        unsafe {
76            check(ffi::chfl_property_get_double(self.as_ptr(), &mut value))?;
77        }
78        return Ok(value);
79    }
80
81    fn get_string(&self) -> Result<String, Error> {
82        let get_string = |ptr, len| unsafe { ffi::chfl_property_get_string(self.as_ptr(), ptr, len) };
83        let value = strings::call_autogrow_buffer(64, get_string)?;
84        return Ok(strings::from_c(value.as_ptr()));
85    }
86
87    fn get_vector3d(&self) -> Result<[f64; 3], Error> {
88        let mut value = [0.0; 3];
89        unsafe {
90            check(ffi::chfl_property_get_vector3d(self.as_ptr(), value.as_mut_ptr()))?;
91        }
92        return Ok(value);
93    }
94}
95
96impl Drop for RawProperty {
97    fn drop(&mut self) {
98        unsafe {
99            let _ = ffi::chfl_free(self.as_ptr().cast());
100        }
101    }
102}
103
104/// A `Property` is a piece of data that can be associated with an `Atom` or a
105/// `Frame`.
106#[derive(Debug, Clone, PartialEq, PartialOrd)]
107pub enum Property {
108    /// Boolean property
109    Bool(bool),
110    /// Floating point property
111    Double(f64),
112    /// String property
113    String(String),
114    /// 3-dimensional vector property
115    Vector3D([f64; 3]),
116}
117
118impl From<bool> for Property {
119    fn from(value: bool) -> Self {
120        Property::Bool(value)
121    }
122}
123
124impl From<f64> for Property {
125    fn from(value: f64) -> Self {
126        Property::Double(value)
127    }
128}
129
130impl From<String> for Property {
131    fn from(value: String) -> Self {
132        Property::String(value)
133    }
134}
135
136impl<'a> From<&'a str> for Property {
137    fn from(value: &'a str) -> Self {
138        Property::String(value.into())
139    }
140}
141
142impl From<[f64; 3]> for Property {
143    fn from(value: [f64; 3]) -> Self {
144        Property::Vector3D(value)
145    }
146}
147
148impl Property {
149    pub(crate) fn as_raw(&self) -> RawProperty {
150        match *self {
151            Property::Bool(value) => RawProperty::bool(value),
152            Property::Double(value) => RawProperty::double(value),
153            Property::String(ref value) => RawProperty::string(value),
154            Property::Vector3D(value) => RawProperty::vector3d(value),
155        }
156    }
157
158    #[allow(clippy::needless_pass_by_value)] // raw property
159    pub(crate) fn from_raw(raw: RawProperty) -> Property {
160        match raw.get_kind() {
161            ffi::chfl_property_kind::CHFL_PROPERTY_BOOL => Self::Bool(raw.get_bool().expect("should be a bool")),
162            ffi::chfl_property_kind::CHFL_PROPERTY_DOUBLE => {
163                Self::Double(raw.get_double().expect("should be a double"))
164            }
165            ffi::chfl_property_kind::CHFL_PROPERTY_STRING => {
166                Self::String(raw.get_string().expect("should be a string"))
167            }
168            ffi::chfl_property_kind::CHFL_PROPERTY_VECTOR3D => {
169                Property::Vector3D(raw.get_vector3d().expect("should be a vector3d"))
170            }
171        }
172    }
173}
174
175/// An iterator over the properties in an atom/frame/residue
176pub struct PropertiesIter<'a> {
177    pub(crate) names: std::vec::IntoIter<String>,
178    pub(crate) getter: Box<dyn Fn(&str) -> Property + 'a>,
179}
180
181impl<'a> Iterator for PropertiesIter<'a> {
182    type Item = (String, Property);
183    fn next(&mut self) -> Option<Self::Item> {
184        self.names.next().map(|name| {
185            let property = (self.getter)(&name);
186            (name, property)
187        })
188    }
189
190    fn size_hint(&self) -> (usize, Option<usize>) {
191        self.names.size_hint()
192    }
193
194    fn count(self) -> usize {
195        self.names.count()
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    mod raw {
202        use super::super::*;
203
204        #[test]
205        fn bool() {
206            let property = RawProperty::bool(false);
207            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_BOOL);
208            assert_eq!(property.get_bool(), Ok(false));
209        }
210
211        #[test]
212        fn double() {
213            let property = RawProperty::double(45.0);
214            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_DOUBLE);
215            assert_eq!(property.get_double(), Ok(45.0));
216        }
217
218        #[test]
219        fn string() {
220            let property = RawProperty::string("test");
221            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_STRING);
222            assert_eq!(property.get_string(), Ok("test".into()));
223        }
224
225        #[test]
226        fn vector3d() {
227            let property = RawProperty::vector3d([1.2, 3.4, 5.6]);
228            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_VECTOR3D);
229            assert_eq!(property.get_vector3d(), Ok([1.2, 3.4, 5.6]));
230        }
231    }
232
233    mod rust {
234        use super::super::*;
235
236        #[test]
237        fn bool() {
238            let property = Property::Bool(false);
239
240            let raw = property.as_raw();
241            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_BOOL);
242            assert_eq!(raw.get_bool(), Ok(false));
243
244            assert_eq!(Property::from_raw(raw), property);
245        }
246
247        #[test]
248        fn double() {
249            let property = Property::Double(45.0);
250
251            let raw = property.as_raw();
252            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_DOUBLE);
253            assert_eq!(raw.get_double(), Ok(45.0));
254
255            assert_eq!(Property::from_raw(raw), property);
256        }
257
258        #[test]
259        fn string() {
260            let property = Property::String("test".into());
261
262            let raw = property.as_raw();
263            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_STRING);
264            assert_eq!(raw.get_string(), Ok("test".into()));
265
266            assert_eq!(Property::from_raw(raw), property);
267
268            let property = Property::String("long string ".repeat(128));
269            let raw = property.as_raw();
270            assert_eq!(raw.get_string(), Ok("long string ".repeat(128)));
271        }
272
273        #[test]
274        fn vector3d() {
275            let property = Property::Vector3D([1.2, 3.4, 5.6]);
276
277            let raw = property.as_raw();
278            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_VECTOR3D);
279            assert_eq!(raw.get_vector3d(), Ok([1.2, 3.4, 5.6]));
280
281            assert_eq!(Property::from_raw(raw), property);
282        }
283    }
284}