viiper_client/
client.rs

1// This file is auto-generated by VIIPER codegen. DO NOT EDIT.
2
3use crate::error::{ProblemJson, ViiperError};
4use crate::types::*;
5use std::io::{Read, Write};
6use std::net::TcpStream;
7
8/// VIIPER management API client (synchronous).
9pub struct ViiperClient {
10    addr: String,
11}
12
13impl ViiperClient {
14    /// Create a new VIIPER client connecting to the specified host and port.
15    pub fn new(host: impl Into<String>, port: u16) -> Self {
16        Self {
17            addr: format!("{}:{}", host.into(), port),
18        }
19    }
20
21    fn do_request<T: for<'de> serde::Deserialize<'de>>(
22        &self,
23        path: &str,
24        payload: Option<&str>,
25    ) -> Result<T, ViiperError> {
26        let mut stream = TcpStream::connect(&self.addr)?;
27
28        stream.write_all(path.as_bytes())?;
29        if let Some(p) = payload {
30            stream.write_all(b" ")?;
31            stream.write_all(p.as_bytes())?;
32        }
33        stream.write_all(b"\0")?;
34
35        let mut buf = Vec::new();
36        stream.read_to_end(&mut buf)?;
37
38        let response = String::from_utf8(buf)
39            .map_err(|_| ViiperError::UnexpectedResponse("invalid UTF-8".into()))?
40            .trim_end_matches('\n')
41            .to_string();
42
43        if response.starts_with("{\"status\":") {
44            let problem: ProblemJson = serde_json::from_str(&response)?;
45            return Err(ViiperError::Protocol(problem));
46        }
47
48        serde_json::from_str(&response).map_err(Into::into)
49    }
50
51    /// BusList: bus/list -> BusListResponse
52    pub fn bus_list(&self) -> Result<BusListResponse, ViiperError> {
53        let path = "bus/list";
54        let payload: Option<String> = None;
55        self.do_request(&path, payload.as_deref())
56    }
57
58    /// BusCreate: bus/create -> BusCreateResponse
59    pub fn bus_create(&self, uint32: Option<u32>) -> Result<BusCreateResponse, ViiperError> {
60        let path = "bus/create";
61        let payload = uint32.map(|v| v.to_string());
62        self.do_request(&path, payload.as_deref())
63    }
64
65    /// BusRemove: bus/remove -> BusRemoveResponse
66    pub fn bus_remove(&self, uint32: Option<u32>) -> Result<BusRemoveResponse, ViiperError> {
67        let path = "bus/remove";
68        let payload = uint32.map(|v| v.to_string());
69        self.do_request(&path, payload.as_deref())
70    }
71
72    /// BusDevicesList: bus/{id}/list -> DevicesListResponse
73    pub fn bus_devices_list(&self, id: u32) -> Result<DevicesListResponse, ViiperError> {
74        let path = format!("bus/{}/list", id);
75        let payload: Option<String> = None;
76        self.do_request(&path, payload.as_deref())
77    }
78
79    /// BusDeviceAdd: bus/{id}/add -> Device
80    pub fn bus_device_add(&self, id: u32, device_create_request: &DeviceCreateRequest) -> Result<Device, ViiperError> {
81        let path = format!("bus/{}/add", id);
82        let payload = Some(serde_json::to_string(&device_create_request)?);
83        self.do_request(&path, payload.as_deref())
84    }
85
86    /// BusDeviceRemove: bus/{id}/remove -> DeviceRemoveResponse
87    pub fn bus_device_remove(&self, id: u32, string: Option<&str>) -> Result<DeviceRemoveResponse, ViiperError> {
88        let path = format!("bus/{}/remove", id);
89        let payload = string.map(|s| s.to_string());
90        self.do_request(&path, payload.as_deref())
91    }
92
93    /// Connect to a device stream for sending input and receiving output.
94    pub fn connect_device(&self, bus_id: u32, dev_id: &str) -> Result<DeviceStream, ViiperError> {
95        DeviceStream::connect(&self.addr, bus_id, dev_id)
96    }
97}
98
99/// A connected device stream for bidirectional communication.
100pub struct DeviceStream {
101    stream: TcpStream,
102    output_thread: Option<std::thread::JoinHandle<()>>,
103}
104
105impl DeviceStream {
106    pub fn connect(addr: &str, bus_id: u32, dev_id: &str) -> Result<Self, ViiperError> {
107        let mut stream = TcpStream::connect(addr)?;
108        let handshake = format!("bus/{}/{}\0", bus_id, dev_id);
109        stream.write_all(handshake.as_bytes())?;
110        Ok(Self { 
111            stream,
112            output_thread: None,
113        })
114    }
115
116    /// Send a device input to the device.
117    pub fn send<T: crate::wire::DeviceInput>(&mut self, input: &T) -> Result<(), ViiperError> {
118        let bytes = input.to_bytes();
119        self.stream.write_all(&bytes)?;
120        Ok(())
121    }
122
123    /// Register a callback to receive device output asynchronously.
124    /// The callback receives a BufRead reader and must read the exact number of bytes expected.
125    /// The callback will be invoked repeatedly on a background thread until it returns an error.
126    /// Only one callback can be registered at a time.
127    pub fn on_output<F>(&mut self, mut callback: F) -> Result<(), ViiperError>
128    where
129        F: FnMut(&mut dyn std::io::BufRead) -> std::io::Result<()> + Send + 'static,
130    {
131        if self.output_thread.is_some() {
132            return Err(ViiperError::UnexpectedResponse("Output callback already registered".into()));
133        }
134
135        let stream = self.stream.try_clone()?;
136        let handle = std::thread::spawn(move || {
137            let mut reader = std::io::BufReader::new(stream);
138            loop {
139                match callback(&mut reader) {
140                    Ok(()) => continue,
141                    Err(_) => break,
142                }
143            }
144        });
145        self.output_thread = Some(handle);
146        Ok(())
147    }
148
149    /// Send raw bytes to the device.
150    pub fn send_raw(&mut self, data: &[u8]) -> Result<(), ViiperError> {
151        self.stream.write_all(data)?;
152        Ok(())
153    }
154
155    /// Read raw bytes from the device.
156    pub fn read_raw(&mut self, buf: &mut [u8]) -> Result<usize, ViiperError> {
157        self.stream.read(buf).map_err(Into::into)
158    }
159
160    /// Read exact number of bytes from the device.
161    pub fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), ViiperError> {
162        self.stream.read_exact(buf).map_err(Into::into)
163    }
164}
165
166impl Drop for DeviceStream {
167    fn drop(&mut self) {
168        let _ = self.stream.shutdown(std::net::Shutdown::Both);
169        if let Some(handle) = self.output_thread.take() {
170            let _ = handle.join();
171        }
172    }
173}