Skip to main content

scpi_client/
lib.rs

1use regex::Regex;
2use thiserror::Error;
3
4pub mod enumerations;
5pub mod primitives;
6
7#[derive(Error, Debug)]
8pub enum Error {
9    // TODO build more errors for decoding of values (string to f32 conversion)
10    //  and unexpected symbols
11    #[error("Received data does not match expected format: {0}")]
12    ResponseDecoding(String),
13}
14
15pub type Result<T> = std::result::Result<T, Error>;
16
17pub trait ScpiSerialize {
18    fn serialize(&self, out: &mut String);
19
20    fn serialize_to_string(&self) -> String {
21        let mut out = String::new();
22        self.serialize(&mut out);
23        out
24    }
25}
26
27pub trait ScpiDeserialize
28where
29    Self: Sized,
30{
31    // TODO maybe this should have an associated type so the implementer
32    // can choose the error type.
33
34    fn deserialize(input: &mut &str) -> Result<Self>;
35
36    fn deserialize_complete(mut input: &str) -> Result<Self> {
37        let result = Self::deserialize(&mut input)?;
38        check_empty(input).unwrap();
39        Ok(result)
40    }
41}
42
43pub trait ScpiRequest: ScpiSerialize {
44    // Note, that the response does intentionally not depend on ScpiDeserialize
45    // because an empty response cannot be deserialized.
46    // TODO maybe this should be modeled better by splitting scpi commands and queries, one with response, one without.
47    type Response;
48}
49
50// TODO remove? is thits truly universal?
51impl<T: ScpiSerialize> ScpiSerialize for Option<T> {
52    fn serialize(&self, out: &mut String) {
53        if let Some(inner) = self {
54            inner.serialize(out);
55        }
56    }
57}
58
59/// Response type to indicate that no answer is expected.
60/// The communication driver will not attempt to receive a
61/// response for an associated request.
62pub struct EmptyResponse;
63
64#[macro_export]
65macro_rules! impl_scpi_serialize {
66    ($type:ty, [ $( $part:tt $(as $converter:ty)? ),* $(,)? ]) => {
67        impl $crate::ScpiSerialize for $type {
68            fn serialize(&self, out: &mut String) {
69                $(
70                    impl_scpi_serialize!(@part self, out, $part $(as $converter)*);
71                )*
72            }
73        }
74    };
75
76    // Handle string literals
77    (@part $self:ident, $out:ident, $lit:literal) => {
78        $out.push_str($lit);
79    };
80
81    // Handle field names
82    (@part $self:ident, $out:ident, $field:ident) => {
83        $self.$field.serialize($out);
84    };
85
86    (@part $self:ident, $out:ident, $field:ident as $converter:ty) => {
87        let convert : $converter = $self.$field.into();
88        convert.serialize($out);
89    };
90}
91
92// TODO naming is bad here with request and structs FooRequest...
93#[macro_export]
94macro_rules! impl_scpi_request {
95    ($request:ty, $response:ty) => {
96        impl $crate::ScpiRequest for $request {
97            type Response = $response;
98        }
99    };
100}
101
102pub fn match_literal(input: &mut &str, literal: &'static str) -> Result<()> {
103    if let Some(rest) = input.strip_prefix(literal) {
104        *input = rest;
105        Ok(())
106    } else {
107        Err(Error::ResponseDecoding(format!(
108            "Expected literal `{literal}` not matched `{input}`"
109        )))
110    }
111}
112
113pub fn read_until<'a>(input: &mut &'a str, delimiter: char) -> Result<&'a str> {
114    if let Some(index) = input.find(delimiter) {
115        let (head, tail) = input.split_at(index);
116        *input = &tail[1..]; // from 1 to skip delimiter
117        Ok(head)
118    } else {
119        Err(Error::ResponseDecoding(format!(
120            "Expected `{delimiter}` in `{input}`"
121        )))
122    }
123}
124
125pub fn read_prefix<'a>(input: &mut &'a str, pattern: &Regex) -> &'a str {
126    let length = pattern.find(input).map_or(0, |m| m.end());
127    let (head, tail) = input.split_at(length);
128    *input = tail;
129    head
130}
131
132pub fn read_exact<'a>(input: &mut &'a str, len: usize) -> Result<&'a str> {
133    if input.len() < len {
134        return Err(Error::ResponseDecoding(format!(
135            "Failed to read {len} characters from `{input}`"
136        )));
137    }
138
139    let (head, tail) = input.split_at(len);
140    *input = tail;
141    Ok(head)
142}
143
144pub fn read_all(input: &mut &str) -> Result<String> {
145    let result = input.to_string();
146    *input = "";
147    Ok(result)
148}
149
150pub fn check_empty(input: &str) -> Result<()> {
151    if input.is_empty() {
152        Ok(())
153    } else {
154        Err(Error::ResponseDecoding(format!(
155            "Response should be empty/fully deserialized, but still has content: `{input}`"
156        )))
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_check_empty() {
166        assert!(check_empty("").is_ok());
167        assert!(check_empty("x").is_err());
168    }
169
170    #[test]
171    fn test_read_exact() {
172        let input = &mut "1234";
173        assert_eq!(read_exact(input, 2).unwrap(), "12");
174        assert!(read_exact(input, 3).is_err());
175        assert_eq!(read_exact(input, 2).unwrap(), "34");
176        assert!(check_empty(input).is_ok());
177    }
178
179    #[test]
180    fn test_match_literal() {
181        let input = &mut "1234";
182        assert!(match_literal(input, "12").is_ok());
183        assert!(match_literal(input, "12").is_err());
184        assert!(match_literal(input, "34").is_ok());
185        assert!(check_empty(input).is_ok());
186    }
187
188    #[test]
189    fn test_read_until() {
190        let input = &mut "12,34";
191        assert_eq!(read_until(input, ',').unwrap(), "12");
192        assert!(match_literal(input, "34").is_ok());
193        assert!(check_empty(input).is_ok());
194    }
195
196    #[test]
197    fn test_read_prefix() {
198        let input = &mut "12,34";
199        let pattern = regex::Regex::new("^[0-9]+").unwrap();
200        assert_eq!(read_prefix(input, &pattern), "12");
201        assert!(match_literal(input, ",").is_ok());
202        assert_eq!(read_prefix(input, &pattern), "34");
203        assert!(check_empty(input).is_ok());
204    }
205
206    #[test]
207    fn test_read_all() {
208        let input = &mut "12,34\nasdf";
209        assert_eq!(read_all(input).unwrap(), "12,34\nasdf");
210    }
211}