Model restructuring
* Remove old Command payload and replace with generic one. * Move Rich Presence model back to src/models. * Add Subscription command, Ready event and Error event models. * Add subscribe method to client and implement simple error detection.
This commit is contained in:
parent
a585bb6495
commit
95d748f211
|
@ -1,8 +1,21 @@
|
|||
use connection::{Connection, SocketConnection};
|
||||
use models::{Handshake, OpCode};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
|
||||
use connection::{
|
||||
Connection,
|
||||
SocketConnection,
|
||||
};
|
||||
use models::{
|
||||
OpCode,
|
||||
Command,
|
||||
Event,
|
||||
payload::Payload,
|
||||
commands::{SubscriptionArgs, Subscription},
|
||||
};
|
||||
|
||||
#[cfg(feature = "rich_presence")]
|
||||
use rich_presence::{SetActivityArgs, SetActivity};
|
||||
use error::Result;
|
||||
use models::rich_presence::{SetActivityArgs, Activity};
|
||||
use error::{Result, Error};
|
||||
use utils;
|
||||
|
||||
|
||||
pub struct Client<T>
|
||||
|
@ -10,14 +23,14 @@ pub struct Client<T>
|
|||
{
|
||||
client_id: u64,
|
||||
version: u32,
|
||||
socket: T,
|
||||
connection: T,
|
||||
}
|
||||
|
||||
impl<T> Client<T>
|
||||
where T: Connection
|
||||
{
|
||||
pub fn with_connection(client_id: u64, socket: T) -> Result<Self> {
|
||||
Ok(Self { version: 1, client_id, socket })
|
||||
pub fn with_connection(client_id: u64, connection: T) -> Result<Self> {
|
||||
Ok(Self { version: 1, client_id, connection })
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> Result<Self> {
|
||||
|
@ -25,13 +38,30 @@ impl<T> Client<T>
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rich_presence")]
|
||||
pub fn set_activity<F>(&mut self, f: F) -> Result<()>
|
||||
where F: FnOnce(SetActivity) -> SetActivity
|
||||
pub fn execute<A, E>(&mut self, cmd: Command, args: A, evt: Option<Event>) -> Result<Payload<E>>
|
||||
where A: Serialize,
|
||||
E: Serialize + DeserializeOwned
|
||||
{
|
||||
let args = SetActivityArgs::command(f(SetActivity::new()));
|
||||
self.socket.send(OpCode::Frame, args)?;
|
||||
Ok(())
|
||||
self.connection.send(OpCode::Frame, Payload::with_nonce(cmd, Some(args), None, evt))?;
|
||||
let response: Payload<E> = self.connection.recv()?.into();
|
||||
|
||||
match response.evt {
|
||||
Some(Event::Error) => Err(Error::SubscriptionFailed),
|
||||
_ => Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rich_presence")]
|
||||
pub fn set_activity<F>(&mut self, f: F) -> Result<Payload<Activity>>
|
||||
where F: FnOnce(Activity) -> Activity
|
||||
{
|
||||
self.execute(Command::SetActivity, SetActivityArgs::new(f), None)
|
||||
}
|
||||
|
||||
pub fn subscribe<F>(&mut self, evt: Event, f: F) -> Result<Payload<Subscription>>
|
||||
where F: FnOnce(SubscriptionArgs) -> SubscriptionArgs
|
||||
{
|
||||
self.execute(Command::Subscribe, f(SubscriptionArgs::new()), Some(evt))
|
||||
}
|
||||
|
||||
// private
|
||||
|
@ -39,7 +69,13 @@ impl<T> Client<T>
|
|||
fn handshake(&mut self) -> Result<()> {
|
||||
let client_id = self.client_id;
|
||||
let version = self.version;
|
||||
self.socket.send(OpCode::Handshake, Handshake::new(client_id, version))?;
|
||||
let hs = json![{
|
||||
"client_id": client_id.to_string(),
|
||||
"v": version,
|
||||
"nonce": utils::nonce()
|
||||
}];
|
||||
self.connection.send(OpCode::Handshake, hs)?;
|
||||
self.connection.recv()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use std::{
|
||||
io::{Write, Read},
|
||||
marker::Sized,
|
||||
fmt::Debug,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use models::{Payload, Message, OpCode};
|
||||
use serde::Serialize;
|
||||
|
||||
use models::message::{Message, OpCode};
|
||||
use error::Result;
|
||||
|
||||
|
||||
|
@ -26,25 +27,25 @@ pub trait Connection
|
|||
}
|
||||
|
||||
fn send<T>(&mut self, opcode: OpCode, payload: T) -> Result<()>
|
||||
where T: Payload + Debug
|
||||
where T: Serialize
|
||||
{
|
||||
debug!("payload: {:#?}", payload);
|
||||
match Message::new(opcode, payload).encode() {
|
||||
let message = Message::new(opcode, payload);
|
||||
debug!("{:?}", message);
|
||||
match message.encode() {
|
||||
Err(why) => error!("{:?}", why),
|
||||
Ok(bytes) => {
|
||||
self.socket().write_all(bytes.as_ref())?;
|
||||
debug!("sent opcode: {:?}", opcode);
|
||||
self.recv()?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv(&mut self) -> Result<Vec<u8>> {
|
||||
fn recv(&mut self) -> Result<Message> {
|
||||
let mut buf: Vec<u8> = vec![0; 1024];
|
||||
let n = self.socket().read(buf.as_mut_slice())?;
|
||||
buf.resize(n, 0);
|
||||
debug!("{:?}", Message::decode(&buf));
|
||||
Ok(buf)
|
||||
let message = Message::decode(&buf)?;
|
||||
debug!("{:?}", message);
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use std::{
|
|||
pub enum Error {
|
||||
Io(IoError),
|
||||
Conversion,
|
||||
SubscriptionFailed,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
|
@ -26,6 +27,7 @@ impl StdError for Error {
|
|||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::Conversion => "Failed to convert values",
|
||||
Error::SubscriptionFailed => "Failed to subscribe to event",
|
||||
Error::Io(ref err) => err.description()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ extern crate log;
|
|||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
extern crate byteorder;
|
||||
extern crate uuid;
|
||||
|
@ -15,12 +16,8 @@ mod macros;
|
|||
mod error;
|
||||
mod utils;
|
||||
mod connection;
|
||||
mod models;
|
||||
mod rich_presence;
|
||||
|
||||
pub mod models;
|
||||
pub mod client;
|
||||
|
||||
pub use client::Client;
|
||||
#[cfg(feature = "rich_presence")]
|
||||
pub use rich_presence::*;
|
||||
pub use connection::{Connection, SocketConnection};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
macro_rules! message_func {
|
||||
macro_rules! builder_func {
|
||||
[ $name:ident, $type:tt func ] => {
|
||||
pub fn $name<F>(mut self, func: F) -> Self
|
||||
where F: FnOnce($type) -> $type
|
||||
|
@ -22,9 +22,9 @@ macro_rules! message_func {
|
|||
};
|
||||
}
|
||||
|
||||
macro_rules! message_format {
|
||||
macro_rules! builder {
|
||||
[ @st ( $name:ident $field:tt: $type:tt alias = $alias:tt, $($rest:tt)* ) -> ( $($out:tt)* ) ] => {
|
||||
message_format![ @st
|
||||
builder![ @st
|
||||
( $name $($rest)* ) -> (
|
||||
$($out)*
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = $alias)]
|
||||
|
@ -34,11 +34,11 @@ macro_rules! message_format {
|
|||
};
|
||||
|
||||
[ @st ( $name:ident $field:tt: $type:tt func, $($rest:tt)* ) -> ( $($out:tt)* ) ] => {
|
||||
message_format![ @st ( $name $field: $type, $($rest)* ) -> ( $($out)* ) ];
|
||||
builder![ @st ( $name $field: $type, $($rest)* ) -> ( $($out)* ) ];
|
||||
};
|
||||
|
||||
[ @st ( $name:ident $field:ident: $type:ty, $($rest:tt)* ) -> ( $($out:tt)* ) ] => {
|
||||
message_format![ @st
|
||||
builder![ @st
|
||||
( $name $($rest)* ) -> (
|
||||
$($out)*
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -48,20 +48,20 @@ macro_rules! message_format {
|
|||
};
|
||||
|
||||
[ @st ( $name:ident ) -> ( $($out:tt)* ) ] => {
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||
pub struct $name { $($out)* }
|
||||
};
|
||||
|
||||
[ @im ( $name:ident $field:ident: $type:tt func, $($rest:tt)* ) -> ( $($out:tt)* ) ] => {
|
||||
message_format![ @im ( $name $($rest)* ) -> ( message_func![$field, $type func]; $($out)* ) ];
|
||||
builder![ @im ( $name $($rest)* ) -> ( builder_func![$field, $type func]; $($out)* ) ];
|
||||
};
|
||||
|
||||
[ @im ( $name:ident $field:ident: $type:tt alias = $modifier:tt, $($rest:tt)* ) -> ( $($out:tt)* ) ] => {
|
||||
message_format![ @im ( $name $field: $type, $($rest)* ) -> ( $($out)* ) ];
|
||||
builder![ @im ( $name $field: $type, $($rest)* ) -> ( $($out)* ) ];
|
||||
};
|
||||
|
||||
[ @im ( $name:ident $field:ident: $type:tt, $($rest:tt)* ) -> ( $($out:tt)* ) ] => {
|
||||
message_format![ @im ( $name $($rest)* ) -> ( message_func![$field, $type]; $($out)* ) ];
|
||||
builder![ @im ( $name $($rest)* ) -> ( builder_func![$field, $type]; $($out)* ) ];
|
||||
};
|
||||
|
||||
[ @im ( $name:ident ) -> ( $($out:tt)* ) ] => {
|
||||
|
@ -75,7 +75,7 @@ macro_rules! message_format {
|
|||
};
|
||||
|
||||
[ $name:ident $($body:tt)* ] => {
|
||||
message_format![@st ( $name $($body)* ) -> () ];
|
||||
message_format![@im ( $name $($body)* ) -> () ];
|
||||
builder![@st ( $name $($body)* ) -> () ];
|
||||
builder![@im ( $name $($body)* ) -> () ];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::Payload;
|
||||
use utils::nonce;
|
||||
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Command<T>
|
||||
where T: Serialize
|
||||
{
|
||||
pub nonce: String,
|
||||
pub cmd: String,
|
||||
pub args: T,
|
||||
}
|
||||
|
||||
impl<T> Command<T>
|
||||
where T: Serialize
|
||||
{
|
||||
pub fn new<S>(cmd: S, args: T) -> Self
|
||||
where S: Into<String>
|
||||
{
|
||||
Command {
|
||||
cmd: cmd.into(),
|
||||
nonce: nonce(),
|
||||
args: args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Payload for Command<T>
|
||||
where T: Serialize {}
|
|
@ -0,0 +1,11 @@
|
|||
use super::shared::PartialUser;
|
||||
|
||||
|
||||
builder!{SubscriptionArgs
|
||||
secret: String, // Activity{Join,Spectate}
|
||||
user: PartialUser, // ActivityJoinRequest
|
||||
}
|
||||
|
||||
builder!{Subscription
|
||||
evt: String,
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
use super::shared::PartialUser;
|
||||
|
||||
|
||||
builder!{ReadyEvent
|
||||
v: u32,
|
||||
config: RpcServerConfiguration,
|
||||
user: PartialUser,
|
||||
}
|
||||
|
||||
builder!{ErrorEvent
|
||||
code: u32,
|
||||
message: String,
|
||||
}
|
||||
|
||||
builder!{RpcServerConfiguration
|
||||
cdn_host: String,
|
||||
api_endpoint: String,
|
||||
environment: String,
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use super::Payload;
|
||||
use utils::nonce;
|
||||
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Handshake {
|
||||
nonce: String,
|
||||
v: u32,
|
||||
client_id: String,
|
||||
}
|
||||
|
||||
impl Handshake {
|
||||
pub fn new(client_id: u64, version: u32) -> Self {
|
||||
Self {
|
||||
nonce: nonce(),
|
||||
v: version,
|
||||
client_id: client_id.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Payload for Handshake {}
|
|
@ -30,40 +30,42 @@ impl OpCode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Message {
|
||||
opcode: OpCode,
|
||||
message: String,
|
||||
pub opcode: OpCode,
|
||||
pub payload: String,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new<T>(opcode: OpCode, message: T) -> Self
|
||||
pub fn new<T>(opcode: OpCode, payload: T) -> Self
|
||||
where T: Serialize
|
||||
{
|
||||
Message {
|
||||
opcode: opcode,
|
||||
message: serde_json::to_string(&message).unwrap()
|
||||
}
|
||||
Self { opcode, payload: serde_json::to_string(&payload).unwrap() }
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Result<Vec<u8>> {
|
||||
let mut bytes: Vec<u8> = vec![];
|
||||
|
||||
bytes.write_u32::<LittleEndian>(self.opcode as u32)?;
|
||||
bytes.write_u32::<LittleEndian>(self.message.len() as u32)?;
|
||||
write!(bytes, "{}", self.message)?;
|
||||
bytes.write_u32::<LittleEndian>(self.payload.len() as u32)?;
|
||||
write!(bytes, "{}", self.payload)?;
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub fn decode(bytes: &[u8]) -> Result<Self> {
|
||||
let mut reader = io::Cursor::new(bytes);
|
||||
let mut message = String::new();
|
||||
let mut payload = String::new();
|
||||
|
||||
let opcode = OpCode::try_from(reader.read_u32::<LittleEndian>()?)?;
|
||||
reader.read_u32::<LittleEndian>()?;
|
||||
reader.read_to_string(&mut message)?;
|
||||
Ok(Message { opcode, message })
|
||||
reader.read_to_string(&mut payload)?;
|
||||
|
||||
Ok(Self { opcode, payload })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -1,11 +1,57 @@
|
|||
mod message;
|
||||
mod command;
|
||||
mod handshake;
|
||||
mod shared;
|
||||
pub mod message;
|
||||
pub mod payload;
|
||||
pub mod commands;
|
||||
pub mod events;
|
||||
pub mod rich_presence;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum Command {
|
||||
Dispatch,
|
||||
Authorize,
|
||||
Subscribe,
|
||||
Unsubscribe,
|
||||
#[cfg(feature = "rich_presence")]
|
||||
SetActivity,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum Event {
|
||||
Ready,
|
||||
Error,
|
||||
#[cfg(feature = "rich_presence")]
|
||||
ActivityJoin,
|
||||
#[cfg(feature = "rich_presence")]
|
||||
ActivitySpectate,
|
||||
#[cfg(feature = "rich_presence")]
|
||||
ActivityJoinRequest,
|
||||
}
|
||||
|
||||
pub use self::message::{Message, OpCode};
|
||||
pub use self::command::Command;
|
||||
pub use self::handshake::Handshake;
|
||||
pub use self::commands::*;
|
||||
pub use self::events::*;
|
||||
|
||||
pub trait Payload: Serialize {}
|
||||
#[cfg(feature = "rich_presence")]
|
||||
pub use self::rich_presence::*;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::Command;
|
||||
pub use super::Event;
|
||||
#[cfg(feature = "rich_presence")]
|
||||
pub use super::rich_presence::{
|
||||
SetActivityArgs,
|
||||
ActivityJoinEvent,
|
||||
ActivitySpectateEvent,
|
||||
ActivityJoinRequestEvent
|
||||
};
|
||||
pub use super::commands::{
|
||||
SubscriptionArgs, Subscription
|
||||
};
|
||||
pub use super::events::{
|
||||
ReadyEvent,
|
||||
ErrorEvent,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
use std::{
|
||||
convert::From,
|
||||
};
|
||||
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use serde_json;
|
||||
|
||||
use super::{Command, Event, Message};
|
||||
use utils;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Payload<T>
|
||||
where T: Serialize
|
||||
{
|
||||
pub cmd: Command,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub args: Option<T>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<T>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub evt: Option<Event>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nonce: Option<String>,
|
||||
}
|
||||
|
||||
impl<T> Payload<T>
|
||||
where T: Serialize
|
||||
{
|
||||
pub fn with_nonce(cmd: Command, args: Option<T>, data: Option<T>, evt: Option<Event>) -> Self {
|
||||
Self { cmd, args, data, evt, nonce: Some(utils::nonce()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Message> for Payload<T>
|
||||
where T: Serialize + DeserializeOwned
|
||||
{
|
||||
fn from(message: Message) -> Self {
|
||||
serde_json::from_str(&message.payload).unwrap()
|
||||
}
|
||||
}
|
|
@ -1,54 +1,69 @@
|
|||
use models::Command;
|
||||
use utils::pid;
|
||||
#![cfg(feature = "rich_presence")]
|
||||
|
||||
use super::shared::PartialUser;
|
||||
use utils;
|
||||
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct SetActivityArgs {
|
||||
pid: i32,
|
||||
activity: SetActivity,
|
||||
activity: Activity,
|
||||
}
|
||||
|
||||
impl SetActivityArgs {
|
||||
pub fn command(args: SetActivity) -> Command<Self> {
|
||||
Command::new("SET_ACTIVITY", Self {
|
||||
pid: pid(),
|
||||
activity: args
|
||||
})
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where F: FnOnce(Activity) -> Activity
|
||||
{
|
||||
Self { pid: utils::pid(), activity: f(Activity::new()) }
|
||||
}
|
||||
}
|
||||
|
||||
message_format![SetActivity
|
||||
state: String,
|
||||
details: String,
|
||||
instance: bool,
|
||||
timestamps: SetActivityTimestamps func,
|
||||
assets: SetActivityAssets func,
|
||||
party: SetActivityParty func,
|
||||
secrets: SetActivitySecrets func,
|
||||
];
|
||||
builder!{ActivityJoinEvent
|
||||
secret: String,
|
||||
}
|
||||
|
||||
message_format![SetActivityTimestamps
|
||||
builder!{ActivitySpectateEvent
|
||||
secret: String,
|
||||
}
|
||||
|
||||
builder!{ActivityJoinRequestEvent
|
||||
user: PartialUser,
|
||||
}
|
||||
|
||||
|
||||
builder!{Activity
|
||||
state: String,
|
||||
details: String,
|
||||
instance: bool,
|
||||
timestamps: ActivityTimestamps func,
|
||||
assets: ActivityAssets func,
|
||||
party: ActivityParty func,
|
||||
secrets: ActivitySecrets func,
|
||||
}
|
||||
|
||||
builder!{ActivityTimestamps
|
||||
start: u32,
|
||||
end: u32,
|
||||
];
|
||||
}
|
||||
|
||||
message_format![SetActivityAssets
|
||||
builder!{ActivityAssets
|
||||
large_image: String,
|
||||
large_text: String,
|
||||
small_image: String,
|
||||
small_text: String,
|
||||
];
|
||||
}
|
||||
|
||||
message_format![SetActivityParty
|
||||
builder!{ActivityParty
|
||||
id: u32,
|
||||
size: (u32, u32),
|
||||
];
|
||||
}
|
||||
|
||||
message_format![SetActivitySecrets
|
||||
builder!{ActivitySecrets
|
||||
join: String,
|
||||
spectate: String,
|
||||
game: String alias = "match",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -86,7 +101,7 @@ r###"{
|
|||
|
||||
#[test]
|
||||
fn test_serialize_full_activity() {
|
||||
let activity = SetActivity::new()
|
||||
let activity = Activity::new()
|
||||
.state("rusting")
|
||||
.details("detailed")
|
||||
.instance(true)
|
||||
|
@ -113,7 +128,7 @@ r###"{
|
|||
|
||||
#[test]
|
||||
fn test_serialize_empty_activity() {
|
||||
let activity = SetActivity::new();
|
||||
let activity = Activity::new();
|
||||
let json = serde_json::to_string(&activity).unwrap();
|
||||
assert_eq![json, "{}"];
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
builder!{PartialUser
|
||||
id: String,
|
||||
username: String,
|
||||
discriminator: String,
|
||||
avatar: String,
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#![cfg(feature = "rich_presence")]
|
||||
|
||||
mod set_activity;
|
||||
|
||||
pub use self::set_activity::*;
|
Loading…
Reference in New Issue