Skip to main content

nanonis_rs/
types.rs

1use crate::error::NanonisError;
2use serde::{Deserialize, Serialize};
3use std::time::Duration;
4
5// ==================== Helpers ====================
6
7/// Safely convert an f32 seconds value (from the server) to a Duration.
8/// Returns a Protocol error if the value is negative, infinite, or NaN.
9pub(crate) fn duration_from_secs_f32(secs: f32) -> Result<Duration, NanonisError> {
10    if secs.is_nan() || secs.is_infinite() || secs < 0.0 {
11        return Err(NanonisError::Protocol(format!(
12            "Invalid duration value: {secs}"
13        )));
14    }
15    Ok(Duration::from_secs_f32(secs))
16}
17
18// ==================== Core Protocol Value Type ====================
19
20#[derive(Debug, Clone)]
21pub enum NanonisValue {
22    U8(u8),
23    U16(u16),
24    I16(i16),
25    U32(u32),
26    I32(i32),
27    F32(f32),
28    F64(f64),
29    String(String),
30    ArrayU8(Vec<u8>),
31    ArrayU32(Vec<u32>),
32    ArrayI32(Vec<i32>),
33    ArrayF32(Vec<f32>),
34    ArrayF64(Vec<f64>),
35    ArrayString(Vec<String>),
36    Array2DF32(Vec<Vec<f32>>),
37}
38
39// From implementations for NanonisValue
40impl From<f32> for NanonisValue {
41    fn from(value: f32) -> Self {
42        NanonisValue::F32(value)
43    }
44}
45
46impl From<f64> for NanonisValue {
47    fn from(value: f64) -> Self {
48        NanonisValue::F64(value)
49    }
50}
51
52impl From<u16> for NanonisValue {
53    fn from(value: u16) -> Self {
54        NanonisValue::U16(value)
55    }
56}
57
58impl From<u32> for NanonisValue {
59    fn from(value: u32) -> Self {
60        NanonisValue::U32(value)
61    }
62}
63
64impl From<i16> for NanonisValue {
65    fn from(value: i16) -> Self {
66        NanonisValue::I16(value)
67    }
68}
69
70impl From<i32> for NanonisValue {
71    fn from(value: i32) -> Self {
72        NanonisValue::I32(value)
73    }
74}
75
76impl From<String> for NanonisValue {
77    fn from(value: String) -> Self {
78        NanonisValue::String(value)
79    }
80}
81
82impl From<Vec<f32>> for NanonisValue {
83    fn from(value: Vec<f32>) -> Self {
84        NanonisValue::ArrayF32(value)
85    }
86}
87
88impl From<Vec<String>> for NanonisValue {
89    fn from(value: Vec<String>) -> Self {
90        NanonisValue::ArrayString(value)
91    }
92}
93
94impl From<Vec<i32>> for NanonisValue {
95    fn from(value: Vec<i32>) -> Self {
96        NanonisValue::ArrayI32(value)
97    }
98}
99
100// TryFrom implementations
101impl TryFrom<NanonisValue> for f32 {
102    type Error = NanonisError;
103
104    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
105        match value {
106            NanonisValue::F32(v) => Ok(v),
107            _ => Err(NanonisError::Protocol(format!(
108                "Expected f32, got {value:?}"
109            ))),
110        }
111    }
112}
113
114impl TryFrom<NanonisValue> for f64 {
115    type Error = NanonisError;
116
117    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
118        match value {
119            NanonisValue::F64(v) => Ok(v),
120            _ => Err(NanonisError::Protocol(format!(
121                "Expected f64, got {value:?}"
122            ))),
123        }
124    }
125}
126
127impl TryFrom<NanonisValue> for u16 {
128    type Error = NanonisError;
129
130    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
131        match value {
132            NanonisValue::U16(v) => Ok(v),
133            _ => Err(NanonisError::Protocol(format!(
134                "Expected u16, got {value:?}"
135            ))),
136        }
137    }
138}
139
140impl TryFrom<NanonisValue> for u32 {
141    type Error = NanonisError;
142
143    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
144        match value {
145            NanonisValue::U32(v) => Ok(v),
146            _ => Err(NanonisError::Protocol(format!(
147                "Expected u32, got {value:?}"
148            ))),
149        }
150    }
151}
152
153impl TryFrom<NanonisValue> for i16 {
154    type Error = NanonisError;
155
156    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
157        match value {
158            NanonisValue::I16(v) => Ok(v),
159            _ => Err(NanonisError::Protocol(format!(
160                "Expected i16, got {value:?}"
161            ))),
162        }
163    }
164}
165
166impl TryFrom<NanonisValue> for i32 {
167    type Error = NanonisError;
168
169    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
170        match value {
171            NanonisValue::I32(v) => Ok(v),
172            _ => Err(NanonisError::Protocol(format!(
173                "Expected i32, got {value:?}"
174            ))),
175        }
176    }
177}
178
179impl TryFrom<NanonisValue> for Vec<f32> {
180    type Error = NanonisError;
181
182    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
183        match value {
184            NanonisValue::ArrayF32(v) => Ok(v),
185            _ => Err(NanonisError::Protocol(format!(
186                "Expected Vec<f32>, got {value:?}"
187            ))),
188        }
189    }
190}
191
192impl TryFrom<NanonisValue> for Vec<String> {
193    type Error = NanonisError;
194
195    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
196        match value {
197            NanonisValue::ArrayString(v) => Ok(v),
198            _ => Err(NanonisError::Protocol(format!(
199                "Expected Vec<String>, got {value:?}"
200            ))),
201        }
202    }
203}
204
205impl TryFrom<NanonisValue> for Vec<i32> {
206    type Error = NanonisError;
207
208    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
209        match value {
210            NanonisValue::ArrayI32(v) => Ok(v),
211            _ => Err(NanonisError::Protocol(format!(
212                "Expected Vec<i32>, got {value:?}"
213            ))),
214        }
215    }
216}
217
218// Convenience methods
219impl NanonisValue {
220    pub fn as_f32(&self) -> Result<f32, NanonisError> {
221        match self {
222            NanonisValue::F32(v) => Ok(*v),
223            _ => Err(NanonisError::Protocol(format!(
224                "Expected f32, got {self:?}"
225            ))),
226        }
227    }
228
229    pub fn as_f64(&self) -> Result<f64, NanonisError> {
230        match self {
231            NanonisValue::F64(v) => Ok(*v),
232            _ => Err(NanonisError::Protocol(format!(
233                "Expected f64, got {self:?}"
234            ))),
235        }
236    }
237
238    pub fn as_u16(&self) -> Result<u16, NanonisError> {
239        match self {
240            NanonisValue::U16(v) => Ok(*v),
241            _ => Err(NanonisError::Protocol(format!(
242                "Expected u16, got {self:?}"
243            ))),
244        }
245    }
246
247    pub fn as_u32(&self) -> Result<u32, NanonisError> {
248        match self {
249            NanonisValue::U32(v) => Ok(*v),
250            _ => Err(NanonisError::Protocol(format!(
251                "Expected u32, got {self:?}"
252            ))),
253        }
254    }
255
256    pub fn as_i16(&self) -> Result<i16, NanonisError> {
257        match self {
258            NanonisValue::I16(v) => Ok(*v),
259            _ => Err(NanonisError::Protocol(format!(
260                "Expected i16, got {self:?}"
261            ))),
262        }
263    }
264
265    pub fn as_i32(&self) -> Result<i32, NanonisError> {
266        match self {
267            NanonisValue::I32(v) => Ok(*v),
268            _ => Err(NanonisError::Protocol(format!(
269                "Expected i32, got {self:?}"
270            ))),
271        }
272    }
273
274    pub fn as_string_array(&self) -> Result<&[String], NanonisError> {
275        match self {
276            NanonisValue::ArrayString(arr) => Ok(arr),
277            _ => Err(NanonisError::Protocol(format!(
278                "Expected string array, got {self:?}"
279            ))),
280        }
281    }
282
283    pub fn as_f32_array(&self) -> Result<&[f32], NanonisError> {
284        match self {
285            NanonisValue::ArrayF32(arr) => Ok(arr),
286            _ => Err(NanonisError::Protocol(format!(
287                "Expected f32 array, got {self:?}"
288            ))),
289        }
290    }
291
292    pub fn as_f64_array(&self) -> Result<&[f64], NanonisError> {
293        match self {
294            NanonisValue::ArrayF64(arr) => Ok(arr),
295            _ => Err(NanonisError::Protocol(format!(
296                "Expected f64 array, got {self:?}"
297            ))),
298        }
299    }
300
301    pub fn as_i32_array(&self) -> Result<&[i32], NanonisError> {
302        match self {
303            NanonisValue::ArrayI32(arr) => Ok(arr),
304            _ => Err(NanonisError::Protocol(format!(
305                "Expected i32 array, got {self:?}"
306            ))),
307        }
308    }
309
310    pub fn as_u32_array(&self) -> Result<&[u32], NanonisError> {
311        match self {
312            NanonisValue::ArrayU32(arr) => Ok(arr),
313            _ => Err(NanonisError::Protocol(format!(
314                "Expected u32 array, got {self:?}"
315            ))),
316        }
317    }
318
319    pub fn as_string(&self) -> Result<&str, NanonisError> {
320        match self {
321            NanonisValue::String(s) => Ok(s),
322            _ => Err(NanonisError::Protocol(format!(
323                "Expected string, got {self:?}"
324            ))),
325        }
326    }
327
328    pub fn as_f32_2d_array(&self) -> Result<&Vec<Vec<f32>>, NanonisError> {
329        match self {
330            NanonisValue::Array2DF32(arr) => Ok(arr),
331            _ => Err(NanonisError::Protocol(format!(
332                "Expected 2D f32 array, got {self:?}"
333            ))),
334        }
335    }
336}
337
338// ==================== Domain-Specific Types ====================
339// Note: Domain-specific types have been moved to their respective modules:
340// - Motor types -> client::motor::types
341// - Scan types -> client::scan::types
342// - Z-controller types -> client::z_ctrl::types
343// - Bias types -> client::bias::types
344// - Signal types -> client::signals::types
345// - Oscilloscope types -> client::oscilloscope::types
346// - TCP Logger types -> client::tcplog::types
347
348// ==================== Position Types ====================
349
350#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
351pub struct Position {
352    pub x: f64,
353    pub y: f64,
354}
355
356impl Position {
357    pub fn new(x: f64, y: f64) -> Self {
358        Self { x, y }
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    // ---- duration_from_secs_f32 ----
367
368    #[test]
369    fn duration_valid_values() {
370        assert_eq!(
371            duration_from_secs_f32(0.0).unwrap(),
372            Duration::from_secs_f32(0.0)
373        );
374        assert_eq!(
375            duration_from_secs_f32(1.5).unwrap(),
376            Duration::from_secs_f32(1.5)
377        );
378        assert_eq!(
379            duration_from_secs_f32(0.001).unwrap(),
380            Duration::from_secs_f32(0.001)
381        );
382    }
383
384    #[test]
385    fn duration_rejects_negative() {
386        assert!(duration_from_secs_f32(-1.0).is_err());
387        assert!(duration_from_secs_f32(-0.001).is_err());
388    }
389
390    #[test]
391    fn duration_rejects_nan() {
392        assert!(duration_from_secs_f32(f32::NAN).is_err());
393    }
394
395    #[test]
396    fn duration_rejects_infinity() {
397        assert!(duration_from_secs_f32(f32::INFINITY).is_err());
398        assert!(duration_from_secs_f32(f32::NEG_INFINITY).is_err());
399    }
400
401    // ---- NanonisValue From impls ----
402
403    #[test]
404    fn from_primitives() {
405        assert!(matches!(NanonisValue::from(1.0f32), NanonisValue::F32(v) if v == 1.0));
406        assert!(matches!(NanonisValue::from(2.0f64), NanonisValue::F64(v) if v == 2.0));
407        assert!(matches!(NanonisValue::from(42u16), NanonisValue::U16(42)));
408        assert!(matches!(NanonisValue::from(100u32), NanonisValue::U32(100)));
409        assert!(matches!(NanonisValue::from(-5i16), NanonisValue::I16(-5)));
410        assert!(matches!(NanonisValue::from(-10i32), NanonisValue::I32(-10)));
411    }
412
413    #[test]
414    fn from_collections() {
415        assert!(
416            matches!(NanonisValue::from("hello".to_string()), NanonisValue::String(s) if s == "hello")
417        );
418        assert!(matches!(
419            NanonisValue::from(vec![1.0f32, 2.0]),
420            NanonisValue::ArrayF32(_)
421        ));
422        assert!(matches!(
423            NanonisValue::from(vec![1i32, 2]),
424            NanonisValue::ArrayI32(_)
425        ));
426    }
427
428    // ---- NanonisValue TryFrom / as_* methods ----
429
430    #[test]
431    fn tryfrom_correct_types() {
432        assert_eq!(f32::try_from(NanonisValue::F32(3.14)).unwrap(), 3.14);
433        assert_eq!(f64::try_from(NanonisValue::F64(2.718)).unwrap(), 2.718);
434        assert_eq!(u16::try_from(NanonisValue::U16(100)).unwrap(), 100);
435        assert_eq!(u32::try_from(NanonisValue::U32(200)).unwrap(), 200);
436        assert_eq!(i16::try_from(NanonisValue::I16(-5)).unwrap(), -5);
437        assert_eq!(i32::try_from(NanonisValue::I32(-10)).unwrap(), -10);
438    }
439
440    #[test]
441    fn tryfrom_wrong_types() {
442        assert!(f32::try_from(NanonisValue::I32(1)).is_err());
443        assert!(u16::try_from(NanonisValue::F32(1.0)).is_err());
444        assert!(Vec::<f32>::try_from(NanonisValue::I32(1)).is_err());
445        assert!(Vec::<String>::try_from(NanonisValue::F32(1.0)).is_err());
446    }
447
448    #[test]
449    fn as_methods_correct() {
450        assert_eq!(NanonisValue::F32(1.0).as_f32().unwrap(), 1.0);
451        assert_eq!(NanonisValue::F64(2.0).as_f64().unwrap(), 2.0);
452        assert_eq!(NanonisValue::U16(10).as_u16().unwrap(), 10);
453        assert_eq!(NanonisValue::U32(20).as_u32().unwrap(), 20);
454        assert_eq!(NanonisValue::I16(-3).as_i16().unwrap(), -3);
455        assert_eq!(NanonisValue::I32(-7).as_i32().unwrap(), -7);
456        assert_eq!(NanonisValue::String("hi".into()).as_string().unwrap(), "hi");
457    }
458
459    #[test]
460    fn as_methods_wrong_type() {
461        assert!(NanonisValue::I32(1).as_f32().is_err());
462        assert!(NanonisValue::F32(1.0).as_u32().is_err());
463        assert!(NanonisValue::U16(1).as_string().is_err());
464    }
465
466    #[test]
467    fn as_array_methods() {
468        let f32_arr = NanonisValue::ArrayF32(vec![1.0, 2.0]);
469        assert_eq!(f32_arr.as_f32_array().unwrap(), &[1.0, 2.0]);
470        assert!(f32_arr.as_f64_array().is_err());
471
472        let str_arr = NanonisValue::ArrayString(vec!["a".into(), "b".into()]);
473        assert_eq!(str_arr.as_string_array().unwrap(), &["a", "b"]);
474        assert!(str_arr.as_f32_array().is_err());
475    }
476}