Migrate tauri from portmaster-ui to desktop/tauri. Update build system
This commit is contained in:
191
desktop/tauri/src-tauri/src/portapi/client.rs
Normal file
191
desktop/tauri/src-tauri/src/portapi/client.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use http::Uri;
|
||||
use log::{debug, error, warn};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_websockets::{ClientBuilder, Error};
|
||||
|
||||
use super::message::*;
|
||||
use super::types::*;
|
||||
|
||||
/// An internal representation of a Command that
|
||||
/// contains the PortAPI message as well as a response
|
||||
/// channel that will receive all responses sent from the
|
||||
/// server.
|
||||
///
|
||||
/// Users should normally not need to use the Command struct
|
||||
/// directly since `PortAPI` already abstracts the creation of
|
||||
/// mpsc channels.
|
||||
struct Command {
|
||||
msg: Message,
|
||||
response: Sender<Response>,
|
||||
}
|
||||
|
||||
/// The client implementation for PortAPI.
|
||||
#[derive(Clone)]
|
||||
pub struct PortAPI {
|
||||
dispatch: Sender<Command>,
|
||||
}
|
||||
|
||||
/// The map type used to store message subscribers.
|
||||
type SubscriberMap = RwLock<HashMap<usize, Sender<Response>>>;
|
||||
|
||||
/// Connect to PortAPI at the specified URI.
|
||||
///
|
||||
/// This method will launch a new async thread on the `tauri::async_runtime`
|
||||
/// that will handle message to transmit and also multiplex server responses
|
||||
/// to the appropriate subscriber.
|
||||
pub async fn connect(uri: &str) -> Result<PortAPI, Error> {
|
||||
let parsed = match uri.parse::<Uri>() {
|
||||
Ok(u) => u,
|
||||
Err(_e) => {
|
||||
return Err(Error::NoUriConfigured); // TODO(ppacher): fix the return error type.
|
||||
}
|
||||
};
|
||||
|
||||
let (mut client, _) = ClientBuilder::from_uri(parsed).connect().await?;
|
||||
let (tx, mut dispatch) = channel::<Command>(64);
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let subscribers: SubscriberMap = RwLock::new(HashMap::new());
|
||||
let next_id = AtomicUsize::new(0);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
msg = client.next() => {
|
||||
let msg = match msg {
|
||||
Some(msg) => msg,
|
||||
None => {
|
||||
warn!("websocket connection lost");
|
||||
|
||||
dispatch.close();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match msg {
|
||||
Err(err) => {
|
||||
error!("failed to receive frame from websocket: {}", err);
|
||||
|
||||
dispatch.close();
|
||||
return;
|
||||
},
|
||||
Ok(msg) => {
|
||||
let text = unsafe {
|
||||
std::str::from_utf8_unchecked(msg.as_payload())
|
||||
};
|
||||
|
||||
match text.parse::<Message>() {
|
||||
Ok(msg) => {
|
||||
let id = msg.id;
|
||||
let map = subscribers
|
||||
.read()
|
||||
.await;
|
||||
|
||||
if let Some(sub) = map.get(&id) {
|
||||
let res: Result<Response, MessageError> = msg.try_into();
|
||||
match res {
|
||||
Ok(response) => {
|
||||
if let Err(err) = sub.send(response).await {
|
||||
// The receiver side has been closed already,
|
||||
// drop the read lock and remove the subscriber
|
||||
// from our hashmap
|
||||
drop(map);
|
||||
|
||||
subscribers
|
||||
.write()
|
||||
.await
|
||||
.remove(&id);
|
||||
|
||||
debug!("subscriber for command {} closed read side: {}", id, err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("invalid command: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("failed to deserialize message: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
Some(mut cmd) = dispatch.recv() => {
|
||||
let id = next_id.fetch_add(1, Ordering::Relaxed);
|
||||
cmd.msg.id = id;
|
||||
let blob: String = cmd.msg.into();
|
||||
|
||||
debug!("Sending websocket frame: {}", blob);
|
||||
|
||||
match client.send(tokio_websockets::Message::text(blob)).await {
|
||||
Ok(_) => {
|
||||
subscribers
|
||||
.write()
|
||||
.await
|
||||
.insert(id, cmd.response);
|
||||
},
|
||||
Err(err) => {
|
||||
error!("failed to dispatch command: {}", err);
|
||||
|
||||
// TODO(ppacher): we should send some error to cmd.response here.
|
||||
// Otherwise, the sender of cmd might get stuck waiting for responses
|
||||
// if they don't check for PortAPI.is_closed().
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PortAPI { dispatch: tx })
|
||||
}
|
||||
|
||||
impl PortAPI {
|
||||
/// `request` sends a PortAPI `portapi::types::Request` to the server and returns a mpsc receiver channel
|
||||
/// where all server responses are forwarded.
|
||||
///
|
||||
/// If the caller does not intend to read any responses the returned receiver may be closed or
|
||||
/// dropped. As soon as the async-thread launched in `connect` detects a closed receiver it is remove
|
||||
/// from the subscription map.
|
||||
///
|
||||
/// The default buffer size for the channel is 64. Use `request_with_buffer_size` to specify a dedicated buffer size.
|
||||
pub async fn request(
|
||||
&self,
|
||||
r: Request,
|
||||
) -> std::result::Result<Receiver<Response>, MessageError> {
|
||||
self.request_with_buffer_size(r, 64).await
|
||||
}
|
||||
|
||||
// Like `request` but supports explicitly specifying a channel buffer size.
|
||||
pub async fn request_with_buffer_size(
|
||||
&self,
|
||||
r: Request,
|
||||
buffer: usize,
|
||||
) -> std::result::Result<Receiver<Response>, MessageError> {
|
||||
let (tx, rx) = channel(buffer);
|
||||
|
||||
let msg: Message = r.try_into()?;
|
||||
|
||||
let _ = self.dispatch.send(Command { response: tx, msg }).await;
|
||||
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
/// Reports whether or not the websocket connection to the Portmaster Database API has been closed
|
||||
/// due to errors.
|
||||
///
|
||||
/// Users are expected to check this field on a regular interval to detect any issues and perform
|
||||
/// a clean re-connect by calling `connect` again.
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.dispatch.is_closed()
|
||||
}
|
||||
}
|
||||
258
desktop/tauri/src-tauri/src/portapi/message.rs
Normal file
258
desktop/tauri/src-tauri/src/portapi/message.rs
Normal file
@@ -0,0 +1,258 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// MessageError describes any error that is encountered when parsing
|
||||
/// PortAPI messages or when converting between the Request/Response types.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MessageError {
|
||||
#[error("missing command id")]
|
||||
MissingID,
|
||||
|
||||
#[error("invalid command id")]
|
||||
InvalidID,
|
||||
|
||||
#[error("missing command")]
|
||||
MissingCommand,
|
||||
|
||||
#[error("missing key")]
|
||||
MissingKey,
|
||||
|
||||
#[error("missing payload")]
|
||||
MissingPayload,
|
||||
|
||||
#[error("unknown or unsupported command: {0}")]
|
||||
UnknownCommand(String),
|
||||
|
||||
#[error(transparent)]
|
||||
InvalidPayload(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
|
||||
/// Payload defines the payload type and content of a PortAPI message.
|
||||
///
|
||||
/// For the time being, only JSON payloads (indicated by a prefixed 'J' of the payload content)
|
||||
/// is directly supported in `Payload::parse()`.
|
||||
///
|
||||
/// For other payload types (like CBOR, BSON, ...) it's the user responsibility to figure out
|
||||
/// appropriate decoding from the `Payload::UNKNOWN` variant.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum Payload {
|
||||
JSON(String),
|
||||
UNKNOWN(String),
|
||||
}
|
||||
|
||||
/// ParseError is returned from `Payload::parse()`.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParseError {
|
||||
#[error(transparent)]
|
||||
JSON(#[from] serde_json::Error),
|
||||
|
||||
#[error("unknown error while parsing")]
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
|
||||
impl Payload {
|
||||
/// Parse the payload into T.
|
||||
///
|
||||
/// Only JSON parsing is supported for now. See [Payload] for more information.
|
||||
pub fn parse<'a, T>(self: &'a Self) -> std::result::Result<T, ParseError>
|
||||
where
|
||||
T: serde::de::Deserialize<'a> {
|
||||
|
||||
match self {
|
||||
Payload::JSON(blob) => Ok(serde_json::from_str::<T>(blob.as_str())?),
|
||||
Payload::UNKNOWN(_) => Err(ParseError::UNKNOWN),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Supports creating a Payload instance from a String.
|
||||
///
|
||||
/// See [Payload] for more information.
|
||||
impl std::convert::From<String> for Payload {
|
||||
fn from(value: String) -> Payload {
|
||||
let mut chars = value.chars();
|
||||
let first = chars.next();
|
||||
let rest = chars.as_str().to_string();
|
||||
|
||||
match first {
|
||||
Some(c) => match c {
|
||||
'J' => Payload::JSON(rest),
|
||||
_ => Payload::UNKNOWN(value),
|
||||
},
|
||||
None => Payload::UNKNOWN("".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Display implementation for Payload that just displays the raw payload.
|
||||
impl std::fmt::Display for Payload {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Payload::JSON(payload) => {
|
||||
write!(f, "J{}", payload)
|
||||
},
|
||||
Payload::UNKNOWN(payload) => {
|
||||
write!(f, "{}", payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message is an internal representation of a PortAPI message.
|
||||
/// Users should more likely use `portapi::types::Request` and `portapi::types::Response`
|
||||
/// instead of directly using `Message`.
|
||||
///
|
||||
/// The struct is still public since it might be useful for debugging or to implement new
|
||||
/// commands not yet supported by the `portapi::types` crate.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Message {
|
||||
pub id: usize,
|
||||
pub cmd: String,
|
||||
pub key: Option<String>,
|
||||
pub payload: Option<Payload>,
|
||||
}
|
||||
|
||||
/// Implementation to marshal a PortAPI message into it's wire-format representation
|
||||
/// (which is a string).
|
||||
///
|
||||
/// Note that this conversion does not check for invalid messages!
|
||||
impl std::convert::From<Message> for String {
|
||||
fn from(value: Message) -> Self {
|
||||
let mut result = "".to_owned();
|
||||
|
||||
result.push_str(value.id.to_string().as_str());
|
||||
result.push_str("|");
|
||||
result.push_str(&value.cmd);
|
||||
|
||||
if let Some(key) = value.key {
|
||||
result.push_str("|");
|
||||
result.push_str(key.as_str());
|
||||
}
|
||||
|
||||
if let Some(payload) = value.payload {
|
||||
result.push_str("|");
|
||||
result.push_str(payload.to_string().as_str())
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// An implementation for `String::parse()` to convert a wire-format representation
|
||||
/// of a PortAPI message to a Message instance.
|
||||
///
|
||||
/// Any errors returned from `String::parse()` will be of type `MessageError`
|
||||
impl std::str::FromStr for Message {
|
||||
type Err = MessageError;
|
||||
|
||||
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
||||
let parts = line.split("|").collect::<Vec<&str>>();
|
||||
|
||||
let id = match parts.get(0) {
|
||||
Some(s) => match (*s).parse::<usize>() {
|
||||
Ok(id) => Ok(id),
|
||||
Err(_) => Err(MessageError::InvalidID),
|
||||
},
|
||||
None => Err(MessageError::MissingID),
|
||||
}?;
|
||||
|
||||
let cmd = match parts.get(1) {
|
||||
Some(s) => Ok(*s),
|
||||
None => Err(MessageError::MissingCommand),
|
||||
}?
|
||||
.to_string();
|
||||
|
||||
let key = parts.get(2)
|
||||
.and_then(|key| Some(key.to_string()));
|
||||
|
||||
let payload : Option<Payload> = parts.get(3)
|
||||
.and_then(|p| Some(p.to_string().into()));
|
||||
|
||||
return Ok(Message {
|
||||
id,
|
||||
cmd,
|
||||
key,
|
||||
payload: payload
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
struct Test {
|
||||
a: i64,
|
||||
s: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_to_string() {
|
||||
let p = Payload::JSON("{}".to_string());
|
||||
assert_eq!(p.to_string(), "J{}");
|
||||
|
||||
let p = Payload::UNKNOWN("some unknown content".to_string());
|
||||
assert_eq!(p.to_string(), "some unknown content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_from_string() {
|
||||
let p: Payload = "J{}".to_string().into();
|
||||
assert_eq!(p, Payload::JSON("{}".to_string()));
|
||||
|
||||
let p: Payload = "some unknown content".to_string().into();
|
||||
assert_eq!(p, Payload::UNKNOWN("some unknown content".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_parse() {
|
||||
let p: Payload = "J{\"a\": 100, \"s\": \"string\"}".to_string().into();
|
||||
|
||||
let t: Test = p.parse()
|
||||
.expect("Expected payload parsing to work");
|
||||
|
||||
assert_eq!(t, Test{
|
||||
a: 100,
|
||||
s: "string".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_message() {
|
||||
let m = "10|insert|some:key|J{}".parse::<Message>()
|
||||
.expect("Expected message to parse");
|
||||
|
||||
assert_eq!(m, Message{
|
||||
id: 10,
|
||||
cmd: "insert".to_string(),
|
||||
key: Some("some:key".to_string()),
|
||||
payload: Some(Payload::JSON("{}".to_string())),
|
||||
});
|
||||
|
||||
let m = "1|done".parse::<Message>()
|
||||
.expect("Expected message to parse");
|
||||
|
||||
assert_eq!(m, Message{
|
||||
id: 1,
|
||||
cmd: "done".to_string(),
|
||||
key: None,
|
||||
payload: None
|
||||
});
|
||||
|
||||
let m = "".parse::<Message>()
|
||||
.expect_err("Expected parsing to fail");
|
||||
if let MessageError::InvalidID = m {} else {
|
||||
panic!("unexpected error value: {}", m)
|
||||
}
|
||||
|
||||
let m = "1".parse::<Message>()
|
||||
.expect_err("Expected parsing to fail");
|
||||
|
||||
if let MessageError::MissingCommand = m {} else {
|
||||
panic!("unexpected error value: {}", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
4
desktop/tauri/src-tauri/src/portapi/mod.rs
Normal file
4
desktop/tauri/src-tauri/src/portapi/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod client;
|
||||
pub mod message;
|
||||
pub mod types;
|
||||
pub mod models;
|
||||
18
desktop/tauri/src-tauri/src/portapi/models/config.rs
Normal file
18
desktop/tauri/src-tauri/src/portapi/models/config.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use serde::*;
|
||||
use super::super::message::Payload;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct BooleanValue {
|
||||
#[serde(rename = "Value")]
|
||||
pub value: Option<bool>,
|
||||
}
|
||||
|
||||
impl TryInto<Payload> for BooleanValue {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<Payload, Self::Error> {
|
||||
let str = serde_json::to_string(&self)?;
|
||||
|
||||
Ok(Payload::JSON(str))
|
||||
}
|
||||
}
|
||||
4
desktop/tauri/src-tauri/src/portapi/models/mod.rs
Normal file
4
desktop/tauri/src-tauri/src/portapi/models/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod spn;
|
||||
pub mod notification;
|
||||
pub mod subsystem;
|
||||
70
desktop/tauri/src-tauri/src/portapi/models/notification.rs
Normal file
70
desktop/tauri/src-tauri/src/portapi/models/notification.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use serde::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct Notification {
|
||||
#[serde(rename = "EventID")]
|
||||
pub event_id: String,
|
||||
|
||||
#[serde(rename = "GUID")]
|
||||
pub guid: String,
|
||||
|
||||
#[serde(rename = "Type")]
|
||||
pub notification_type: NotificationType,
|
||||
|
||||
#[serde(rename = "Message")]
|
||||
pub message: String,
|
||||
|
||||
#[serde(rename = "Title")]
|
||||
pub title: String,
|
||||
#[serde(rename = "Category")]
|
||||
pub category: String,
|
||||
|
||||
#[serde(rename = "EventData")]
|
||||
pub data: serde_json::Value,
|
||||
|
||||
#[serde(rename = "Expires")]
|
||||
pub expires: u64,
|
||||
|
||||
#[serde(rename = "State")]
|
||||
pub state: String,
|
||||
|
||||
#[serde(rename = "AvailableActions")]
|
||||
pub actions: Vec<Action>,
|
||||
|
||||
#[serde(rename = "SelectedActionID")]
|
||||
pub selected_action_id: String,
|
||||
|
||||
#[serde(rename = "ShowOnSystem")]
|
||||
pub show_on_system: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct Action {
|
||||
#[serde(rename = "ID")]
|
||||
pub id: String,
|
||||
|
||||
#[serde(rename = "Text")]
|
||||
pub text: String,
|
||||
|
||||
#[serde(rename = "Type")]
|
||||
pub action_type: String,
|
||||
|
||||
#[serde(rename = "Payload")]
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct NotificationType(i32);
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const INFO: NotificationType = NotificationType(0);
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const WARN: NotificationType = NotificationType(1);
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const PROMPT: NotificationType = NotificationType(2);
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const ERROR: NotificationType = NotificationType(3);
|
||||
|
||||
8
desktop/tauri/src-tauri/src/portapi/models/spn.rs
Normal file
8
desktop/tauri/src-tauri/src/portapi/models/spn.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use serde::*;
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct SPNStatus {
|
||||
#[serde(rename = "Status")]
|
||||
pub status: String,
|
||||
}
|
||||
45
desktop/tauri/src-tauri/src/portapi/models/subsystem.rs
Normal file
45
desktop/tauri/src-tauri/src/portapi/models/subsystem.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
#![allow(dead_code)]
|
||||
use serde::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct ModuleStatus {
|
||||
#[serde(rename = "Name")]
|
||||
pub name: String,
|
||||
|
||||
#[serde(rename = "Enabled")]
|
||||
pub enabled: bool,
|
||||
|
||||
#[serde(rename = "Status")]
|
||||
pub status: u8,
|
||||
|
||||
#[serde(rename = "FailureStatus")]
|
||||
pub failure_status: u8,
|
||||
|
||||
#[serde(rename = "FailureID")]
|
||||
pub failure_id: String,
|
||||
|
||||
#[serde(rename = "FailureMsg")]
|
||||
pub failure_msg: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Subsystem {
|
||||
#[serde(rename = "ID")]
|
||||
pub id: String,
|
||||
|
||||
#[serde(rename = "Name")]
|
||||
pub name: String,
|
||||
|
||||
#[serde(rename = "Description")]
|
||||
pub description: String,
|
||||
|
||||
#[serde(rename = "Modules")]
|
||||
pub module_status: Vec<ModuleStatus>,
|
||||
|
||||
#[serde(rename = "FailureStatus")]
|
||||
pub failure_status: u8,
|
||||
}
|
||||
pub const FAILURE_NONE: u8 = 0;
|
||||
pub const FAILURE_HINT: u8 = 1;
|
||||
pub const FAILURE_WARNING: u8 = 2;
|
||||
pub const FAILURE_ERROR: u8 = 3;
|
||||
199
desktop/tauri/src-tauri/src/portapi/types.rs
Normal file
199
desktop/tauri/src-tauri/src/portapi/types.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
|
||||
use super::message::*;
|
||||
|
||||
/// Request is a strongly typed request message
|
||||
/// that can be converted to a `portapi::message::Message`
|
||||
/// object for further use by the client (`portapi::client::PortAPI`).
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Request {
|
||||
Get(String),
|
||||
Query(String),
|
||||
Subscribe(String),
|
||||
QuerySubscribe(String),
|
||||
Create(String, Payload),
|
||||
Update(String, Payload),
|
||||
Insert(String, Payload),
|
||||
Delete(String),
|
||||
Cancel,
|
||||
}
|
||||
|
||||
/// Implementation to convert a internal `portapi::message::Message` to a valid
|
||||
/// `Request` variant.
|
||||
///
|
||||
/// Any error returned will be of type `portapi::message::MessageError`.
|
||||
impl std::convert::TryFrom<Message> for Request {
|
||||
type Error = MessageError;
|
||||
|
||||
fn try_from(value: Message) -> Result<Self, Self::Error> {
|
||||
match value.cmd.as_str() {
|
||||
"get" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
Ok(Request::Get(key))
|
||||
},
|
||||
"query" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
Ok(Request::Query(key))
|
||||
},
|
||||
"sub" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
Ok(Request::Subscribe(key))
|
||||
},
|
||||
"qsub" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
Ok(Request::QuerySubscribe(key))
|
||||
},
|
||||
"create" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
let payload = value.payload.ok_or(MessageError::MissingPayload)?;
|
||||
Ok(Request::Create(key, payload))
|
||||
},
|
||||
"update" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
let payload = value.payload.ok_or(MessageError::MissingPayload)?;
|
||||
Ok(Request::Update(key, payload))
|
||||
},
|
||||
"insert" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
let payload = value.payload.ok_or(MessageError::MissingPayload)?;
|
||||
Ok(Request::Insert(key, payload))
|
||||
},
|
||||
"delete" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
Ok(Request::Delete(key))
|
||||
},
|
||||
"cancel" => {
|
||||
Ok(Request::Cancel)
|
||||
},
|
||||
cmd => {
|
||||
Err(MessageError::UnknownCommand(cmd.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An implementation to try to convert a `Request` variant into a valid
|
||||
/// `portapi::message::Message` struct.
|
||||
///
|
||||
/// While this implementation does not yet return any errors, it's expected that
|
||||
/// additional validation will be added in the future so users should already expect
|
||||
/// to receive `portapi::message::MessageError`s.
|
||||
impl std::convert::TryFrom<Request> for Message {
|
||||
type Error = MessageError;
|
||||
|
||||
fn try_from(value: Request) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Request::Get(key) => Ok(Message { id: 0, cmd: "get".to_string(), key: Some(key), payload: None }),
|
||||
Request::Query(key) => Ok(Message { id: 0, cmd: "query".to_string(), key: Some(key), payload: None }),
|
||||
Request::Subscribe(key) => Ok(Message { id: 0, cmd: "sub".to_string(), key: Some(key), payload: None }),
|
||||
Request::QuerySubscribe(key) => Ok(Message { id: 0, cmd: "qsub".to_string(), key: Some(key), payload: None }),
|
||||
Request::Create(key, value) => Ok(Message{ id: 0, cmd: "create".to_string(), key: Some(key), payload: Some(value)}),
|
||||
Request::Update(key, value) => Ok(Message{ id: 0, cmd: "update".to_string(), key: Some(key), payload: Some(value)}),
|
||||
Request::Insert(key, value) => Ok(Message{ id: 0, cmd: "insert".to_string(), key: Some(key), payload: Some(value)}),
|
||||
Request::Delete(key) => Ok(Message { id: 0, cmd: "delete".to_string(), key: Some(key), payload: None }),
|
||||
Request::Cancel => Ok(Message { id: 0, cmd: "cancel".to_string(), key: None, payload: None }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Response is strongly types PortAPI response message.
|
||||
/// that can be converted to a `portapi::message::Message`
|
||||
/// object for further use by the client (`portapi::client::PortAPI`).
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Response {
|
||||
Ok(String, Payload),
|
||||
Update(String, Payload),
|
||||
New(String, Payload),
|
||||
Delete(String),
|
||||
Success,
|
||||
Error(String),
|
||||
Warning(String),
|
||||
Done
|
||||
}
|
||||
|
||||
/// Implementation to convert a internal `portapi::message::Message` to a valid
|
||||
/// `Response` variant.
|
||||
///
|
||||
/// Any error returned will be of type `portapi::message::MessageError`.
|
||||
impl std::convert::TryFrom<Message> for Response {
|
||||
type Error = MessageError;
|
||||
|
||||
fn try_from(value: Message) -> Result<Self, MessageError> {
|
||||
match value.cmd.as_str() {
|
||||
"ok" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
let payload = value.payload.ok_or(MessageError::MissingPayload)?;
|
||||
|
||||
Ok(Response::Ok(key, payload))
|
||||
},
|
||||
"upd" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
let payload = value.payload.ok_or(MessageError::MissingPayload)?;
|
||||
|
||||
Ok(Response::Update(key, payload))
|
||||
},
|
||||
"new" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
let payload = value.payload.ok_or(MessageError::MissingPayload)?;
|
||||
|
||||
Ok(Response::New(key, payload))
|
||||
},
|
||||
"del" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
|
||||
Ok(Response::Delete(key))
|
||||
},
|
||||
"success" => {
|
||||
Ok(Response::Success)
|
||||
},
|
||||
"error" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
|
||||
Ok(Response::Error(key))
|
||||
},
|
||||
"warning" => {
|
||||
let key = value.key.ok_or(MessageError::MissingKey)?;
|
||||
|
||||
Ok(Response::Warning(key))
|
||||
},
|
||||
"done" => {
|
||||
Ok(Response::Done)
|
||||
},
|
||||
cmd => Err(MessageError::UnknownCommand(cmd.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An implementation to try to convert a `Response` variant into a valid
|
||||
/// `portapi::message::Message` struct.
|
||||
///
|
||||
/// While this implementation does not yet return any errors, it's expected that
|
||||
/// additional validation will be added in the future so users should already expect
|
||||
/// to receive `portapi::message::MessageError`s.
|
||||
impl std::convert::TryFrom<Response> for Message {
|
||||
type Error = MessageError;
|
||||
|
||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Response::Ok(key, payload) => Ok(Message{id: 0, cmd: "ok".to_string(), key: Some(key), payload: Some(payload)}),
|
||||
Response::Update(key, payload) => Ok(Message{id: 0, cmd: "upd".to_string(), key: Some(key), payload: Some(payload)}),
|
||||
Response::New(key, payload) => Ok(Message{id: 0, cmd: "new".to_string(), key: Some(key), payload: Some(payload)}),
|
||||
Response::Delete(key ) => Ok(Message{id: 0, cmd: "del".to_string(), key: Some(key), payload: None}),
|
||||
Response::Success => Ok(Message{id: 0, cmd: "success".to_string(), key: None, payload: None}),
|
||||
Response::Warning(key) => Ok(Message{id: 0, cmd: "warning".to_string(), key: Some(key), payload: None}),
|
||||
Response::Error(key) => Ok(Message{id: 0, cmd: "error".to_string(), key: Some(key), payload: None}),
|
||||
Response::Done => Ok(Message{id: 0, cmd: "done".to_string(), key: None, payload: None}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct Record {
|
||||
pub created: u64,
|
||||
pub deleted: u64,
|
||||
pub expires: u64,
|
||||
pub modified: u64,
|
||||
pub key: String,
|
||||
}
|
||||
Reference in New Issue
Block a user