senstate/
lib.rs

1#![doc(html_root_url = "https://docs.rs/senstate/0.0.1")]
2
3use std::sync::mpsc::{channel, Sender};
4use std::thread::JoinHandle;
5
6use log::{trace, warn};
7use num_traits::Num;
8use serde::Serialize;
9use serde_repr::Serialize_repr;
10use tungstenite::{connect, Message};
11use url::Url;
12use uuid::Uuid;
13
14#[derive(Serialize)]
15#[serde(rename_all = "camelCase")]
16struct AppMeta {
17    pub app_id: Uuid,
18    pub name: String,
19}
20
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize_repr)]
22#[repr(u8)]
23/// Different types of data that a watcher can send to Senstate.
24pub enum WatchType {
25    /// String is a simple string of any kind. It can be send with
26    /// [send_string](struct.Watcher.html#method.send_string).
27    String,
28    /// Number can be any number like integers and floats. It can be send with
29    /// [send_number](struct.Watcher.html#method.send_number).
30    Number,
31    /// Json any instance that is serializable to JSON with serde. It can be send with
32    /// [send_json](struct.Watcher.html#method.send_json).
33    Json,
34    /// Performance is currently equivalent to [`Number`](#variant.Number). It can be send with
35    /// [send_performance](struct.Watcher.html#method.send_performance).
36    Performance,
37}
38
39#[derive(Serialize)]
40#[serde(rename_all = "camelCase")]
41struct WatcherMeta {
42    pub watch_id: Uuid,
43    pub tag: String,
44    #[serde(rename = "type")]
45    pub type_: WatchType,
46}
47
48#[derive(Serialize)]
49#[serde(rename_all = "camelCase")]
50struct WatchData<T> {
51    pub watch_id: Uuid,
52    pub data: T,
53}
54
55#[derive(Serialize)]
56#[serde(rename_all = "camelCase")]
57struct LogData<T> {
58    pub log: T,
59}
60
61/// A client is the main connection to the Senstate server and is used to create new
62/// [`Watcher`](struct.Watcher.html)s for sending data.
63#[derive(Debug)]
64pub struct Client {
65    handle: JoinHandle<()>,
66    tx: Sender<String>,
67}
68
69#[derive(Serialize)]
70struct Payload<T> {
71    event: &'static str,
72    data: T,
73}
74
75impl Client {
76    /// Create a new watcher to report the status of a single value.
77    /// The `tag` is a name for this watcher and the `type_` defines which
78    /// kind of data can be reported.
79    pub fn new_watcher<T>(&self, tag: T, type_: WatchType) -> Watcher
80    where
81        T: Into<String>,
82    {
83        let id = Uuid::new_v4();
84        Self::add_watcher(
85            &self.tx,
86            WatcherMeta {
87                watch_id: id,
88                tag: tag.into(),
89                type_,
90            },
91        );
92
93        Watcher {
94            tx: self.tx.clone(),
95            id,
96            type_,
97        }
98    }
99
100    /// Send a log message to Senstate. This can be any data that is serializable.
101    pub fn log<T>(&self, log: T)
102    where
103        T: Serialize,
104    {
105        input_log_event(&self.tx, LogData { log })
106    }
107
108    fn connect(url: Url) -> (JoinHandle<()>, Sender<String>) {
109        let (mut socket, _) = connect(url).expect("Can't connect");
110
111        let (tx, rx) = channel();
112        let handle = std::thread::spawn(move || loop {
113            let msg: String = rx.recv().unwrap();
114
115            trace!("--> {}", msg);
116            socket.write_message(Message::Text(msg)).unwrap();
117        });
118
119        (handle, tx)
120    }
121
122    fn add_app(tx: &Sender<String>, meta: AppMeta) {
123        send(tx, "addApp", &meta);
124    }
125
126    fn add_watcher(tx: &Sender<String>, meta: WatcherMeta) {
127        send(tx, "addWatcher", &meta);
128    }
129
130    /// Wait for the client to shut down.
131    pub fn wait(self) {
132        self.handle.join().unwrap();
133    }
134}
135
136/// A watcher represents the status of a single value. It is bound to a specific type of value
137/// and can update that state at any time.
138#[derive(Debug, Clone)]
139pub struct Watcher {
140    tx: Sender<String>,
141    id: Uuid,
142    type_: WatchType,
143}
144
145impl Watcher {
146    /// Update the string value of this watcher. This method should only be used if
147    /// the watcher type is [`String`](enum.WatchType.html#variant.String).
148    pub fn send_string<T>(&self, data: T)
149    where
150        T: Into<String>,
151    {
152        if self.type_ != WatchType::String {
153            warn!("trying to send string with a {:?} watcher", self.type_);
154        }
155
156        input_event(
157            &self.tx,
158            WatchData {
159                watch_id: self.id,
160                data: data.into(),
161            },
162        )
163    }
164
165    /// Update the number (integer, float, ...) value of this watcher. This method should
166    /// only be used if the watcher type is [`Number`](enum.WatchType.html#variant.Number).
167    pub fn send_number<T>(&self, data: T)
168    where
169        T: Num + Serialize,
170    {
171        if self.type_ != WatchType::Number {
172            warn!("trying to send number with a {:?} watcher", self.type_);
173        }
174
175        input_event(
176            &self.tx,
177            WatchData {
178                watch_id: self.id,
179                data,
180            },
181        )
182    }
183
184    /// Update the JSON value of this watcher. This method should only be used if the watcher
185    /// type is [`Json`](enum.WatchType.html#variant.Json).
186    pub fn send_json<T>(&self, data: T)
187    where
188        T: Serialize,
189    {
190        if self.type_ != WatchType::Json {
191            warn!("trying to send JSON with a {:?} watcher", self.type_);
192        }
193
194        input_event(
195            &self.tx,
196            WatchData {
197                watch_id: self.id,
198                data,
199            },
200        )
201    }
202
203    /// Update the performance value (same as `send_number` currently) of this watcher. This
204    /// method should only be used if the watcher type is
205    /// [`Performance`](enum.WatchType.html#variant.Performance).
206    pub fn send_performance<T>(&self, data: T)
207    where
208        T: Num + Serialize,
209    {
210        if self.type_ != WatchType::Performance {
211            warn!("trying to send performance with a {:?} watcher", self.type_);
212        }
213
214        input_event(
215            &self.tx,
216            WatchData {
217                watch_id: self.id,
218                data,
219            },
220        )
221    }
222
223    /// Send a log message to Senstate. This can be any data that is serializable.
224    pub fn log<T>(&self, log: T)
225    where
226        T: Serialize,
227    {
228        input_log_event(&self.tx, LogData { log })
229    }
230}
231
232/// Create a new Senstate client that can create watchers for specific values or
233/// send logs to Senstate.
234pub fn new<T>(url: Url, name: T) -> Client
235where
236    T: Into<String>,
237{
238    let (handle, tx) = Client::connect(url);
239
240    Client::add_app(
241        &tx,
242        AppMeta {
243            app_id: Uuid::new_v4(),
244            name: name.into(),
245        },
246    );
247
248    Client { handle, tx }
249}
250
251fn send<T>(tx: &Sender<String>, event: &'static str, data: &T)
252where
253    T: Serialize,
254{
255    let data = Payload { event, data };
256    let data = serde_json::to_string(&data).unwrap();
257
258    tx.send(data).unwrap();
259}
260
261fn input_event<T>(tx: &Sender<String>, event: WatchData<T>)
262where
263    T: Serialize,
264{
265    send(tx, "inputEvent", &event);
266}
267
268fn input_log_event<T>(tx: &Sender<String>, event: LogData<T>)
269where
270    T: Serialize,
271{
272    send(tx, "inputLogEvent", &event);
273}