use crate::data::enumeration::EnumString;
use crate::data::{ApiError, Event, Request, RequestType, Response, ResponseType};
use crate::error::{Error, UnexpectedResponseError};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::value::RawValue;
use std::borrow::Cow;
use std::convert::TryFrom;
use std::fmt;
pub const API_NAME: &'static str = "VTubeStudioPublicAPI";
pub const API_VERSION: &'static str = "1.0";
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequestId(smol_str::SmolStr);
impl RequestId {
pub fn new(value: String) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn into_string(self) -> String {
String::from(self.0)
}
}
impl From<String> for RequestId {
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<&str> for RequestId {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl fmt::Display for RequestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct OpaqueValue(Box<RawValue>);
#[cfg(test)]
impl PartialEq for OpaqueValue {
fn eq(&self, rhs: &Self) -> bool {
let left = self.deserialize::<serde_json::Value>();
let right = rhs.deserialize::<serde_json::Value>();
matches!((left, right), (Ok(a), Ok(b)) if a == b)
}
}
impl OpaqueValue {
pub fn new<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
Ok(Self(serde_json::value::to_raw_value(value)?))
}
pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
Ok(serde_json::from_str(self.0.get())?)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
#[serde(rename_all = "camelCase")]
pub struct RequestEnvelope {
pub api_name: Cow<'static, str>,
pub api_version: Cow<'static, str>,
#[serde(rename = "requestID")]
pub request_id: Option<RequestId>,
pub message_type: EnumString<RequestType>,
pub data: OpaqueValue,
}
impl Default for RequestEnvelope {
fn default() -> Self {
Self {
api_name: Cow::Borrowed(API_NAME),
api_version: Cow::Borrowed(API_VERSION),
message_type: EnumString::new(RequestType::ApiStateRequest),
request_id: None,
data: OpaqueValue::default(),
}
}
}
impl RequestEnvelope {
pub fn new<Req: Request>(data: &Req) -> Result<Self, serde_json::Error> {
let mut value = Self::default();
value.set_data(data)?;
Ok(value)
}
pub fn set_data<Req: Request>(&mut self, data: &Req) -> Result<(), serde_json::Error> {
self.message_type = Req::MESSAGE_TYPE.into();
self.data = OpaqueValue::new(data)?;
Ok(())
}
pub fn with_id<T: Into<Option<RequestId>>>(mut self, id: T) -> Self {
self.request_id = id.into();
self
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ResponseEnvelope {
pub api_name: Cow<'static, str>,
pub api_version: Cow<'static, str>,
pub timestamp: i64,
pub request_id: RequestId,
pub data: Result<ResponseData, ApiError>,
}
const API_ERROR_MESSAGE_TYPE: &'static EnumString<ResponseType> =
&EnumString::new(ResponseType::ApiError);
impl ResponseEnvelope {
pub fn new<Resp>(data: &Resp) -> Result<Self, serde_json::Error>
where
Resp: Response + Serialize,
{
let mut value = Self::default();
value.set_data(data)?;
Ok(value)
}
pub fn with_id(mut self, id: RequestId) -> Self {
self.request_id = id;
self
}
pub fn set_data<Resp>(&mut self, data: &Resp) -> Result<(), serde_json::Error>
where
Resp: Response + Serialize,
{
self.data = Ok(ResponseData {
message_type: Resp::MESSAGE_TYPE.into(),
data: OpaqueValue::new(data)?,
});
Ok(())
}
pub fn message_type(&self) -> &EnumString<ResponseType> {
match &self.data {
Ok(data) => &data.message_type,
Err(_) => &API_ERROR_MESSAGE_TYPE,
}
}
pub fn parse<Resp: Response>(self) -> Result<Resp, Error> {
let data = self.data?;
if data.message_type == Resp::MESSAGE_TYPE {
Ok(data.data.deserialize()?)
} else {
Err(UnexpectedResponseError {
expected: Resp::MESSAGE_TYPE,
received: data.message_type,
}
.into())
}
}
pub fn parse_event(self) -> Result<Event, Error> {
let data = self.data?;
Ok(Event::try_from(data)?)
}
pub fn is_api_error(&self) -> bool {
self.data.is_err()
}
pub fn is_unauthenticated_error(&self) -> bool {
matches!(&self.data, Err(e) if e.is_unauthenticated())
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ResponseData {
pub message_type: EnumString<ResponseType>,
pub data: OpaqueValue,
}
impl<'de> Deserialize<'de> for ResponseEnvelope {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawResponseEnvelope<'a> {
#[serde(borrow)]
pub api_name: Cow<'a, str>,
#[serde(borrow)]
pub api_version: Cow<'a, str>,
pub timestamp: i64,
#[serde(rename = "requestID")]
pub request_id: RequestId,
pub message_type: EnumString<ResponseType>,
pub data: OpaqueValue,
}
let raw = RawResponseEnvelope::deserialize(deserializer)?;
let data = if raw.message_type == ResponseType::ApiError {
Err(raw.data.deserialize().map_err(serde::de::Error::custom)?)
} else {
Ok(ResponseData {
message_type: raw.message_type,
data: raw.data,
})
};
let api_name = if raw.api_name == API_NAME {
Cow::Borrowed(API_NAME)
} else {
Cow::Owned(raw.api_name.into_owned())
};
let api_version = if raw.api_version == API_VERSION {
Cow::Borrowed(API_VERSION)
} else {
Cow::Owned(raw.api_version.into_owned())
};
Ok(Self {
api_name,
api_version,
timestamp: raw.timestamp,
request_id: raw.request_id,
data,
})
}
}
impl Serialize for ResponseEnvelope {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct RawResponseEnvelope<'a, T> {
pub api_name: &'a str,
pub api_version: &'a str,
pub timestamp: i64,
#[serde(rename = "requestID")]
pub request_id: &'a RequestId,
pub message_type: &'a EnumString<ResponseType>,
pub data: &'a T,
}
match &self.data {
Ok(inner) => RawResponseEnvelope {
api_name: &self.api_name,
api_version: &self.api_version,
timestamp: self.timestamp,
request_id: &self.request_id,
message_type: &inner.message_type,
data: &inner.data,
}
.serialize(serializer),
Err(e) => RawResponseEnvelope {
api_name: &self.api_name,
api_version: &self.api_version,
timestamp: self.timestamp,
request_id: &self.request_id,
message_type: &ApiError::MESSAGE_TYPE,
data: &e,
}
.serialize(serializer),
}
}
}
impl Default for ResponseEnvelope {
fn default() -> Self {
Self {
api_name: API_NAME.into(),
api_version: API_VERSION.into(),
timestamp: 0,
request_id: RequestId::default(),
data: Ok(ResponseData {
message_type: EnumString::const_new_from_str("UnknownResponse"),
data: OpaqueValue::default(),
}),
}
}
}