use crate::{
core::{
service_response::{
APIErrorBody, APISuccessBodyWithFlattenedPayload, APISuccessBodyWithMessage,
APISuccessBodyWithPayload,
},
PubNubError,
},
lib::{
alloc::{string::String, vec, vec::Vec},
collections::HashMap,
core::ops::Deref,
},
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HeartbeatResult;
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HeartbeatResponseBody {
ErrorResponse(APIErrorBody),
SuccessResponse(APISuccessBodyWithMessage),
}
impl TryFrom<HeartbeatResponseBody> for HeartbeatResult {
type Error = PubNubError;
fn try_from(value: HeartbeatResponseBody) -> Result<Self, Self::Error> {
match value {
HeartbeatResponseBody::SuccessResponse(_) => Ok(HeartbeatResult),
HeartbeatResponseBody::ErrorResponse(resp) => Err(resp.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LeaveResult;
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LeaveResponseBody {
SuccessResponse(APISuccessBodyWithMessage),
ErrorResponse(APIErrorBody),
}
impl TryFrom<LeaveResponseBody> for LeaveResult {
type Error = PubNubError;
fn try_from(value: LeaveResponseBody) -> Result<Self, Self::Error> {
match value {
LeaveResponseBody::SuccessResponse(_) => Ok(LeaveResult),
LeaveResponseBody::ErrorResponse(resp) => Err(resp.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetStateResult {
#[cfg(feature = "serde")]
state: serde_json::Value,
#[cfg(not(feature = "serde"))]
state: Vec<u8>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SetStateResponseBody {
#[cfg(feature = "serde")]
SuccessResponse(APISuccessBodyWithPayload<serde_json::Value>),
#[cfg(not(feature = "serde"))]
SuccessResponse(APISuccessBodyWithPayload<Vec<u8>>),
ErrorResponse(APIErrorBody),
}
impl TryFrom<SetStateResponseBody> for SetStateResult {
type Error = PubNubError;
fn try_from(value: SetStateResponseBody) -> Result<Self, Self::Error> {
match value {
SetStateResponseBody::SuccessResponse(response) => Ok(SetStateResult {
state: response.payload,
}),
SetStateResponseBody::ErrorResponse(resp) => Err(resp.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetStateResult {
pub states: Vec<GetStateInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetStateInfo {
pub channel: String,
#[cfg(feature = "serde")]
pub state: serde_json::Value,
#[cfg(not(feature = "serde"))]
pub state: Vec<u8>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GetStateResponseBody {
SuccessResponse(APISuccessBodyWithPayload<GetStateSuccessBody>),
ErrorResponse(APIErrorBody),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct GetStateSuccessBody {
#[cfg(feature = "serde")]
pub channels: HashMap<String, serde_json::Value>,
#[cfg(not(feature = "serde"))]
pub channels: HashMap<String, Vec<u8>>,
}
impl TryFrom<GetStateResponseBody> for GetStateResult {
type Error = PubNubError;
fn try_from(value: GetStateResponseBody) -> Result<Self, Self::Error> {
match value {
GetStateResponseBody::SuccessResponse(response) => Ok(GetStateResult {
states: response
.payload
.channels
.into_iter()
.map(|(k, v)| GetStateInfo {
channel: k,
state: v,
})
.collect(),
}),
GetStateResponseBody::ErrorResponse(resp) => Err(resp.into()),
}
}
}
impl Deref for GetStateResult {
type Target = Vec<GetStateInfo>;
fn deref(&self) -> &Self::Target {
&self.states
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HereNowResult {
pub channels: Vec<HereNowChannel>,
pub total_channels: u32,
pub total_occupancy: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HereNowChannel {
pub name: String,
pub occupancy: u32,
pub occupants: Vec<HereNowUser>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HereNowUser {
pub user_id: String,
#[cfg(feature = "serde")]
pub state: Option<serde_json::Value>,
#[cfg(not(feature = "serde"))]
pub state: Option<Vec<u8>>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HereNowResponseBody {
SuccessResponse(HereNowResponseSuccessBody),
ErrorResponse(APIErrorBody),
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HereNowResponseSuccessBody {
SingleChannel(APISuccessBodyWithFlattenedPayload<HereNowResponseChannelIdentifier>),
MultipleChannels(APISuccessBodyWithPayload<HereNowResponseChannels>),
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HereNowResponseChannels {
pub total_channels: u32,
pub total_occupancy: u32,
pub channels: HashMap<String, HereNowResponseChannelIdentifier>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HereNowResponseChannelIdentifier {
pub occupancy: u32,
pub uuids: Option<Vec<HereNowResponseUserIdentifier>>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HereNowResponseUserIdentifier {
String(String),
WithState {
uuid: String,
#[cfg(feature = "serde")]
state: serde_json::Value,
#[cfg(not(feature = "serde"))]
state: Vec<u8>,
},
Map {
uuid: String,
},
}
impl TryFrom<HereNowResponseBody> for HereNowResult {
type Error = PubNubError;
fn try_from(value: HereNowResponseBody) -> Result<Self, Self::Error> {
match value {
HereNowResponseBody::SuccessResponse(resp) => Ok(match resp {
HereNowResponseSuccessBody::SingleChannel(single) => {
let occupancy = single.payload.occupancy;
let occupants = single
.payload
.uuids
.map(|maybe_uuids| {
maybe_uuids
.iter()
.map(|uuid| match uuid {
HereNowResponseUserIdentifier::String(uuid) => HereNowUser {
user_id: uuid.clone(),
state: None,
},
HereNowResponseUserIdentifier::Map { uuid } => HereNowUser {
user_id: uuid.clone(),
state: None,
},
HereNowResponseUserIdentifier::WithState { uuid, state } => {
HereNowUser {
user_id: uuid.clone(),
state: Some(state.clone()),
}
}
})
.collect()
})
.unwrap_or_default();
let channels = vec![HereNowChannel {
name: "".into(),
occupancy,
occupants,
}];
Self {
channels,
total_channels: 1,
total_occupancy: occupancy,
}
}
HereNowResponseSuccessBody::MultipleChannels(multiple) => {
let total_channels = multiple.payload.total_channels;
let total_occupancy = multiple.payload.total_occupancy;
let channels = multiple
.payload
.channels
.into_iter()
.map(|(name, channel)| {
let occupancy = channel.occupancy;
let occupants = channel
.uuids
.map(|maybe_uuids| {
maybe_uuids
.into_iter()
.map(|uuid| match uuid {
HereNowResponseUserIdentifier::String(uuid) => {
HereNowUser {
user_id: uuid.clone(),
state: None,
}
}
HereNowResponseUserIdentifier::Map { uuid } => {
HereNowUser {
user_id: uuid.clone(),
state: None,
}
}
HereNowResponseUserIdentifier::WithState {
uuid,
state,
} => HereNowUser {
user_id: uuid.clone(),
state: Some(state.clone()),
},
})
.collect()
})
.unwrap_or_default();
HereNowChannel {
name,
occupancy,
occupants,
}
})
.collect();
Self {
channels,
total_channels,
total_occupancy,
}
}
}),
HereNowResponseBody::ErrorResponse(resp) => Err(resp.into()),
}
}
}
impl Deref for HereNowResult {
type Target = Vec<HereNowChannel>;
fn deref(&self) -> &Self::Target {
&self.channels
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WhereNowResult {
pub channels: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
pub enum WhereNowResponseBody {
SuccessResponse(APISuccessBodyWithPayload<WhereNowResponseSuccessBody>),
ErrorResponse(APIErrorBody),
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WhereNowResponseSuccessBody {
pub channels: Vec<String>,
}
impl TryFrom<WhereNowResponseBody> for WhereNowResult {
type Error = PubNubError;
fn try_from(value: WhereNowResponseBody) -> Result<Self, Self::Error> {
match value {
WhereNowResponseBody::SuccessResponse(resp) => Ok(Self {
channels: resp.payload.channels,
}),
WhereNowResponseBody::ErrorResponse(resp) => Err(resp.into()),
}
}
}
impl Deref for WhereNowResult {
type Target = Vec<String>;
fn deref(&self) -> &Self::Target {
&self.channels
}
}
#[cfg(test)]
mod it_should {
use std::collections::HashMap;
use super::*;
#[test]
fn parse_heartbeat_response() {
let body = HeartbeatResponseBody::SuccessResponse(APISuccessBodyWithMessage {
status: 200,
message: "OK".into(),
service: "Presence".into(),
});
let result: Result<HeartbeatResult, PubNubError> = body.try_into();
assert_eq!(result.unwrap(), HeartbeatResult);
}
#[test]
fn parse_heartbeat_error_response() {
let body = HeartbeatResponseBody::ErrorResponse(APIErrorBody::AsObjectWithService {
status: 400,
error: true,
service: "service".into(),
message: "error".into(),
});
let result: Result<HeartbeatResult, PubNubError> = body.try_into();
assert!(result.is_err());
}
#[test]
fn parse_leave_response() {
let body = LeaveResponseBody::SuccessResponse(APISuccessBodyWithMessage {
status: 200,
message: "OK".into(),
service: "Presence".into(),
});
let result: Result<LeaveResult, PubNubError> = body.try_into();
assert_eq!(result.unwrap(), LeaveResult);
}
#[test]
fn parse_leave_error_response() {
let body = LeaveResponseBody::ErrorResponse(APIErrorBody::AsObjectWithService {
status: 400,
error: true,
service: "service".into(),
message: "error".into(),
});
let result: Result<LeaveResult, PubNubError> = body.try_into();
assert!(result.is_err());
}
#[test]
fn parse_set_state_response() {
use serde_json::json;
let payload_value = json!(HashMap::<String, String>::from([(
"key".into(),
"value".into()
)]));
let body = SetStateResponseBody::SuccessResponse(APISuccessBodyWithPayload {
status: 200,
message: "OK".into(),
payload: payload_value.clone(),
service: "Presence".into(),
});
let result: Result<SetStateResult, PubNubError> = body.try_into();
assert!(payload_value.is_object());
assert_eq!(
result.unwrap(),
SetStateResult {
state: payload_value
}
);
}
#[test]
fn parse_set_state_error_response() {
let body = SetStateResponseBody::ErrorResponse(APIErrorBody::AsObjectWithService {
status: 400,
error: true,
service: "service".into(),
message: "error".into(),
});
let result: Result<SetStateResult, PubNubError> = body.try_into();
assert!(result.is_err());
}
#[test]
fn parse_here_now_response_single_channel() {
use serde_json::json;
let input = json!({
"status":200,
"message":"OK",
"occupancy":1,
"uuids":[
"just_me"
],
"service":"Presence"
});
let result: HereNowResult = serde_json::from_value::<HereNowResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
assert_eq!(result.total_channels, 1);
assert_eq!(result.total_occupancy, 1);
assert_eq!(result.len(), 1);
assert_eq!(result.first().unwrap().name, "");
assert_eq!(result.first().unwrap().occupancy, 1);
assert_eq!(
result.first().unwrap().occupants.first().unwrap().user_id,
"just_me"
);
assert_eq!(
result.first().unwrap().occupants.first().unwrap().state,
None
);
}
#[test]
fn parse_here_now_response_single_channel_with_state() {
use serde_json::json;
let input = json!({
"message": "OK",
"occupancy": 2,
"service": "Presence",
"status": 200,
"uuids": [
{
"state": {
"channel1-state": [
"channel-1-random-value"
]
},
"uuid": "Earline"
},
{
"state": {
"channel1-state": [
"channel-1-random-value"
]
},
"uuid": "Glen"
}
]
});
let result: HereNowResult = serde_json::from_value::<HereNowResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
assert_eq!(result.total_channels, 1);
assert_eq!(result.total_occupancy, 2);
assert_eq!(result.len(), 1);
assert!(result.iter().any(|channel| channel.name.is_empty()));
assert!(result.iter().any(|channel| channel.occupancy == 2));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().user_id == "Earline"));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().state
== Some(json!({"channel1-state": ["channel-1-random-value"]}))));
}
#[test]
fn parse_here_now_response_multiple_channels() {
use serde_json::json;
let input = json!({
"status":200,
"message":"OK",
"payload":{
"channels":{
"my_channel":{
"occupancy":1,
"uuids":[
"pn-200543f2-b394-4909-9e7b-987848e44729"
]
},
"kekw":{
"occupancy":1,
"uuids":[
"just_me"
]
}
},
"total_channels":2,
"total_occupancy":2
},
"service":"Presence"
});
let result: HereNowResult = serde_json::from_value::<HereNowResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
assert_eq!(result.total_channels, 2);
assert_eq!(result.total_occupancy, 2);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|channel| channel.name == "my_channel"));
assert!(result.iter().any(|channel| channel.occupancy == 1));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().user_id
== "pn-200543f2-b394-4909-9e7b-987848e44729"));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().state.is_none()));
}
#[test]
fn parse_here_now_response_multiple_channels_with_state() {
use serde_json::json;
let input = json!({
"message": "OK",
"payload": {
"channels": {
"test-channel1": {
"occupancy": 1,
"uuids": [
{
"state": {
"channel1-state": [
"channel-1-random-value"
]
},
"uuid": "Kim"
}
]
},
"test-channel2": {
"occupancy": 1,
"uuids": [
{
"state": {
"channel2-state": [
"channel-2-random-value"
]
},
"uuid": "Earline"
}
]
}
},
"total_channels": 2,
"total_occupancy": 2
},
"service": "Presence",
"status": 200
});
let result: HereNowResult = serde_json::from_value::<HereNowResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
assert_eq!(result.total_channels, 2);
assert_eq!(result.total_occupancy, 2);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|channel| channel.name == "test-channel1"));
assert!(result.iter().any(|channel| channel.occupancy == 1));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().user_id == "Kim"));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().state
== Some(json!({"channel1-state": ["channel-1-random-value"]}))));
}
#[test]
fn parse_here_now_response_single_channel_with_map_uuid() {
use serde_json::json;
let input = json!({
"status":200,
"message":"OK",
"occupancy":1,
"uuids":[
{"uuid":"just_me"}
],
"service":"Presence"
});
let result: HereNowResult = serde_json::from_value::<HereNowResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
assert_eq!(result.total_channels, 1);
assert_eq!(result.total_occupancy, 1);
assert_eq!(result.len(), 1);
assert_eq!(result.first().unwrap().name, "");
assert_eq!(result.first().unwrap().occupancy, 1);
assert_eq!(
result.first().unwrap().occupants.first().unwrap().user_id,
"just_me"
);
assert_eq!(
result.first().unwrap().occupants.first().unwrap().state,
None
);
}
#[test]
fn parse_here_now_response_multiple_channels_with_map_uuid() {
use serde_json::json;
let input = json!({
"status":200,
"message":"OK",
"payload":{
"channels":{
"my_channel":{
"occupancy":1,
"uuids":[
{"uuid":"pn-200543f2-b394-4909-9e7b-987848e44729"}
]
},
"kekw":{
"occupancy":1,
"uuids":[
{"uuid":"just_me"}
]
}
},
"total_channels":2,
"total_occupancy":2
},
"service":"Presence"
});
let result: HereNowResult = serde_json::from_value::<HereNowResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
assert_eq!(result.total_channels, 2);
assert_eq!(result.total_occupancy, 2);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|channel| channel.name == "my_channel"));
assert!(result.iter().any(|channel| channel.occupancy == 1));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().user_id
== "pn-200543f2-b394-4909-9e7b-987848e44729"));
assert!(result
.iter()
.any(|channel| channel.occupants.first().unwrap().state.is_none()));
}
#[test]
fn parse_here_now_error_response() {
let body = HereNowResponseBody::ErrorResponse(APIErrorBody::AsObjectWithService {
status: 400,
error: true,
service: "service".into(),
message: "error".into(),
});
let result: Result<HereNowResult, PubNubError> = body.try_into();
assert!(result.is_err());
}
#[test]
fn parse_where_now_response() {
use serde_json::json;
let input = json!({
"status":200,
"message":"OK",
"payload":{
"channels":[
"my_channel"
]
},
"service":"Presence"
});
let result: WhereNowResult = serde_json::from_value::<WhereNowResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
result
.channels
.iter()
.any(|channel| channel == "my_channel");
}
#[test]
fn parse_where_now_error_response() {
let body = WhereNowResponseBody::ErrorResponse(APIErrorBody::AsObjectWithService {
status: 400,
error: true,
service: "service".into(),
message: "error".into(),
});
let result: Result<WhereNowResult, PubNubError> = body.try_into();
assert!(result.is_err());
}
#[test]
fn parse_get_state_response() {
use serde_json::json;
let input = json!({
"status": 200,
"message": "OK",
"payload": {
"channels": {
"channel-1": {
"key-1": "value-1",
"key-2": "value-2"
},
"channel-2": {
"key-1": "value-1",
"key-2": "value-2"
}
},
},
"service": "Presence"
});
let result: GetStateResult = serde_json::from_value::<GetStateResponseBody>(input)
.unwrap()
.try_into()
.unwrap();
result.iter().any(|channel| {
channel.channel == "channel-1"
&& channel.state
== json!({
"key-1": "value-1",
"key-2": "value-2"
})
});
}
#[test]
fn parse_get_state_error_response() {
let body = GetStateResponseBody::ErrorResponse(APIErrorBody::AsObjectWithService {
status: 400,
error: true,
service: "service".into(),
message: "error".into(),
});
let result: Result<GetStateResult, PubNubError> = body.try_into();
assert!(result.is_err());
}
}