1#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct BrowseNode {
13 pub tag_id: String,
14 pub node_type: String,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct TagValue {
20 pub tag_id: String,
21 pub value: String,
22 pub quality: String,
23 pub timestamp: String,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct WriteResult {
36 pub tag_id: String,
37 pub success: bool,
38 pub error: Option<String>,
39}
40
41#[derive(Debug, Clone, PartialEq)]
43pub enum Value {
44 String(String),
45 Int(i32),
46 Float(f64),
47 Bool(bool),
48}
49
50pub fn parse_value(raw: &str) -> Value {
59 if let Ok(b) = raw.parse::<bool>() {
60 return Value::Bool(b);
61 }
62 if let Ok(i) = raw.parse::<i32>() {
63 return Value::Int(i);
64 }
65 if let Ok(f) = raw.parse::<f64>() {
66 return Value::Float(f);
67 }
68 Value::String(raw.to_string())
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_parse_value_bool_true() {
77 assert!(matches!(parse_value("true"), Value::Bool(true)));
78 }
79
80 #[test]
81 fn test_parse_value_bool_false() {
82 assert!(matches!(parse_value("false"), Value::Bool(false)));
83 }
84
85 #[test]
86 fn test_parse_value_int_positive() {
87 assert!(matches!(parse_value("42"), Value::Int(42)));
88 }
89
90 #[test]
91 fn test_parse_value_int_negative() {
92 assert!(matches!(parse_value("-1"), Value::Int(-1)));
93 }
94
95 #[test]
96 fn test_parse_value_int_zero() {
97 assert!(matches!(parse_value("0"), Value::Int(0)));
98 }
99
100 #[test]
101 fn test_parse_value_float_positive() {
102 assert!(matches!(parse_value("9.5"), Value::Float(v) if (v - 9.5).abs() < f64::EPSILON));
103 }
104
105 #[test]
106 fn test_parse_value_float_negative() {
107 assert!(matches!(parse_value("-2.5"), Value::Float(v) if (v + 2.5).abs() < f64::EPSILON));
108 }
109
110 #[test]
111 fn test_parse_value_float_exponential() {
112 assert!(matches!(parse_value("1e10"), Value::Float(v) if (v - 1e10).abs() < 1.0));
113 }
114
115 #[test]
116 fn test_parse_value_string_simple() {
117 assert!(matches!(parse_value("hello"), Value::String(s) if s == "hello"));
118 }
119
120 #[test]
121 fn test_parse_value_string_empty() {
122 assert!(matches!(parse_value(""), Value::String(s) if s.is_empty()));
123 }
124
125 #[test]
126 fn test_parse_value_string_numeric_string() {
127 assert!(matches!(parse_value("42foo"), Value::String(s) if s == "42foo"));
128 }
129
130 #[test]
131 fn test_parse_value_string_special_chars() {
132 assert!(matches!(parse_value("hello world!"), Value::String(s) if s == "hello world!"));
133 }
134
135 #[test]
136 fn test_browse_node_fields() {
137 let node = BrowseNode {
138 tag_id: "Simulink".into(),
139 node_type: "Branch".into(),
140 };
141 assert_eq!(node.tag_id, "Simulink");
142 assert_eq!(node.node_type, "Branch");
143 }
144
145 #[test]
146 fn test_tag_value_fields() {
147 let value = TagValue {
148 tag_id: "t1".into(),
149 value: "42".into(),
150 quality: "Good".into(),
151 timestamp: "now".into(),
152 };
153 assert_eq!(value.tag_id, "t1");
154 assert_eq!(value.value, "42");
155 assert_eq!(value.quality, "Good");
156 assert_eq!(value.timestamp, "now");
157 }
158
159 #[test]
160 fn test_write_result_success_has_no_error() {
161 let result = WriteResult {
162 tag_id: "t1".into(),
163 success: true,
164 error: None,
165 };
166 assert!(result.success);
167 assert_eq!(result.error, None);
168 }
169
170 #[test]
171 fn test_write_result_failure_carries_error() {
172 let result = WriteResult {
173 tag_id: "t1".into(),
174 success: false,
175 error: Some("access denied".into()),
176 };
177 assert!(!result.success);
178 assert_eq!(result.error, Some("access denied".to_string()));
179 }
180}