Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add get_presence_state to presence API #171

Merged
merged 15 commits into from
Aug 30, 2023
56 changes: 33 additions & 23 deletions examples/presence_state.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use pubnub::{Keyset, PubNubClientBuilder};
use serde::Serialize;
use std::env;

#[derive(Debug, Serialize)]
struct State {
is_doing: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn snafu::Error>> {
let publish_key = env::var("SDK_PUB_KEY")?;
let subscribe_key = env::var("SDK_SUB_KEY")?;

let _client = PubNubClientBuilder::with_reqwest_transport()
let client = PubNubClientBuilder::with_reqwest_transport()
.with_keyset(Keyset {
subscribe_key,
publish_key: Some(publish_key),
Expand All @@ -17,27 +23,31 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {

println!("running!");

// client
// .set_presence_state()
// .channels(["my_channel".into(), "other_channel".into()].to_vec())
// .state("{\"What you're doing\": \"Me? Nothing... Just hanging around\"}")
// .user_id("user_id")
// .execute()
// .await?;
//
// let states = client
// .get_presence_state()
// .channels(["my_channel".into(), "other_channel".into()].to_vec())
// .user_id("user_id")
// .execute()
// .await?;
//
// println!("All channels state: {:?}", states);
//
// states.iter().for_each(|channel| {
// println!("Channel: {}", channel.channel);
// println!("State: {:?}", channel.state);
// });
//
client
.set_presence_state(State {
is_doing: "Nothing... Just hanging around...".into(),
})
.channels(["my_channel".into(), "other_channel".into()].to_vec())
.user_id("user_id")
.execute()
.await?;

println!("State set!");
println!();

let states = client
.get_presence_state()
.channels(["my_channel".into(), "other_channel".into()].to_vec())
.user_id("user_id")
.execute()
.await?;

println!("All channels state: {:?}", states);

states.iter().for_each(|channel| {
println!("Channel: {}", channel.channel);
println!("State: {:?}", channel.state);
});

Ok(())
}
52 changes: 31 additions & 21 deletions examples/presence_state_blocking.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
use pubnub::{Keyset, PubNubClientBuilder};
use serde::Serialize;
use std::env;

#[derive(Debug, Serialize)]
struct State {
is_doing: String,
}

fn main() -> Result<(), Box<dyn snafu::Error>> {
let publish_key = env::var("SDK_PUB_KEY")?;
let subscribe_key = env::var("SDK_SUB_KEY")?;

let _client = PubNubClientBuilder::with_reqwest_blocking_transport()
let client = PubNubClientBuilder::with_reqwest_blocking_transport()
.with_keyset(Keyset {
subscribe_key,
publish_key: Some(publish_key),
Expand All @@ -16,25 +22,29 @@ fn main() -> Result<(), Box<dyn snafu::Error>> {

println!("running!");

// client
// .set_presence_state()
// .channels(["my_channel".into(), "other_channel".into()].to_vec())
// .state("{\"What you're doing\": \"Me? Nothing... Just hanging around\"}")
// .user_id("user_id")
// .execute_blocking()?;
//
// let states = client
// .get_presence_state()
// .channels(["my_channel".into(), "other_channel".into()].to_vec())
// .user_id("user_id")
// .execute_blocking()?;
//
// println!("All channels state: {:?}", states);
//
// states.iter().for_each(|channel| {
// println!("Channel: {}", channel.channel);
// println!("State: {:?}", channel.state);
// });
//
client
.set_presence_state(State {
is_doing: "Nothing... Just hanging around...".into(),
})
.channels(["my_channel".into(), "other_channel".into()].to_vec())
.user_id("user_id")
.execute_blocking()?;

println!("State set!");
println!();

let states = client
.get_presence_state()
.channels(["my_channel".into(), "other_channel".into()].to_vec())
.user_id("user_id")
.execute_blocking()?;

println!("All channels state: {:?}", states);

states.iter().for_each(|channel| {
println!("Channel: {}", channel.channel);
println!("State: {:?}", channel.state);
});

Ok(())
}
168 changes: 168 additions & 0 deletions src/dx/presence/builders/get_presence_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//! # PubNub set state module.
//!
//! The [`GetStateRequestBuilder`] lets you make and execute requests that will
//! associate the provided `state` with `user_id` on the provided list of
//! channels and channels in channel groups.

use derive_builder::Builder;

use crate::{
core::{
utils::{
encoding::{
url_encode_extended, url_encoded_channel_groups, url_encoded_channels,
UrlEncodeExtension,
},
headers::{APPLICATION_JSON, CONTENT_TYPE},
},
Deserializer, PubNubError, Transport, TransportMethod, TransportRequest,
},
dx::{
presence::{
builders,
result::{GetStateResponseBody, GetStateResult},
},
pubnub_client::PubNubClientInstance,
},
lib::{
alloc::{
string::{String, ToString},
vec,
},
collections::HashMap,
},
};

/// The [`GetStateRequestBuilder`] is used to build `user_id` associated state
/// update request that is sent to the [`PubNub`] network.
///
/// This struct is used by the [`set_state`] method of the [`PubNubClient`].
/// The [`set_state`] method is used to update state associated with `user_id`
/// on the provided channels and groups.
///
/// [`PubNub`]:https://www.pubnub.com/
#[derive(Builder)]
#[builder(
pattern = "owned",
build_fn(vis = "pub(in crate::dx::presence)", validate = "Self::validate"),
no_std
)]
pub struct GetStateRequest<T, D> {
/// Current client which can provide transportation to perform the request.
///
/// This field is used to get [`Transport`] to perform the request.
#[builder(field(vis = "pub(in crate::dx::presence)"), setter(custom))]
pub(in crate::dx::presence) pubnub_client: PubNubClientInstance<T, D>,

/// Channels with which state will be associated.
#[builder(
field(vis = "pub(in crate::dx::presence)"),
setter(strip_option, into),
default = "vec![]"
)]
pub(in crate::dx::presence) channels: Vec<String>,

/// Channel groups with which state will be associated.
///
/// The specified state will be associated with channels that have been
/// included in the specified target channel groups.
#[builder(
field(vis = "pub(in crate::dx::presence)"),
setter(into, strip_option),
default = "vec![]"
)]
pub(in crate::dx::presence) channel_groups: Vec<String>,

#[builder(field(vis = "pub(in crate::dx::presence)"), setter(strip_option, into))]
/// Identifier for which `state` should be associated for provided list of
/// channels and groups.
pub(in crate::dx::presence) user_id: String,
}

impl<T, D> GetStateRequestBuilder<T, D> {
/// Validate user-provided data for request builder.
///
/// Validator ensure that list of provided data is enough to build valid
/// set state request instance.
fn validate(&self) -> Result<(), String> {
let groups_len = self.channel_groups.as_ref().map_or_else(|| 0, |v| v.len());
let channels_len = self.channels.as_ref().map_or_else(|| 0, |v| v.len());

builders::validate_configuration(&self.pubnub_client).and_then(|_| {
if channels_len == groups_len && channels_len == 0 {
Err("Either channels or channel groups should be provided".into())
} else if self.user_id.is_none() {
Err("User id is missing".into())
} else {
Ok(())
}
})
}

/// Build [`GetStateRequest`] from builder.
fn request(self) -> Result<GetStateRequest<T, D>, PubNubError> {
self.build()
.map_err(|err| PubNubError::general_api_error(err.to_string(), None, None))
}
}

impl<T, D> GetStateRequest<T, D> {
/// Create transport request from the request builder.
pub(in crate::dx::presence) fn transport_request(
&self,
) -> Result<TransportRequest, PubNubError> {
let sub_key = &self.pubnub_client.config.subscribe_key;
let mut query: HashMap<String, String> = HashMap::new();

// Serialize list of channel groups and add into query parameters list.
url_encoded_channel_groups(&self.channel_groups)
.and_then(|channel_groups| query.insert("channel-group".into(), channel_groups));

Ok(TransportRequest {
path: format!(
"/v2/presence/sub-key/{sub_key}/channel/{}/uuid/{}",
url_encoded_channels(&self.channels),
url_encode_extended(self.user_id.as_bytes(), UrlEncodeExtension::NonChannelPath)
),
query_parameters: query,
method: TransportMethod::Get,
headers: [(CONTENT_TYPE.into(), APPLICATION_JSON.into())].into(),
body: None,
})
}
}

impl<T, D> GetStateRequestBuilder<T, D>
where
T: Transport,
D: Deserializer + 'static,
{
/// Build and call asynchronous request.
pub async fn execute(self) -> Result<GetStateResult, PubNubError> {
let request = self.request()?;
let transport_request = request.transport_request()?;
let client = request.pubnub_client.clone();
let deserializer = client.deserializer.clone();
transport_request
.send::<GetStateResponseBody, _, _, _>(&client.transport, deserializer)
.await
}
}

#[allow(dead_code)]
#[cfg(feature = "blocking")]
impl<T, D> GetStateRequestBuilder<T, D>
where
T: crate::core::blocking::Transport,
D: Deserializer + 'static,
{
/// Build and call synchronous request.
pub fn execute_blocking(self) -> Result<GetStateResult, PubNubError> {
let request = self.request()?;
let transport_request = request.transport_request()?;
let client = request.pubnub_client.clone();
let deserializer = client.deserializer.clone();
transport_request
.send_blocking::<GetStateResponseBody, _, _, _>(&client.transport, deserializer)
}
}
4 changes: 4 additions & 0 deletions src/dx/presence/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub(crate) mod here_now;
pub(crate) use where_now::WhereNowRequestBuilder;
pub(crate) mod where_now;

#[doc(inline)]
pub(crate) use get_presence_state::GetStateRequestBuilder;
pub(crate) mod get_presence_state;

use crate::{dx::pubnub_client::PubNubClientInstance, lib::alloc::string::String};

/// Validate [`PubNubClient`] configuration.
Expand Down
42 changes: 42 additions & 0 deletions src/dx/presence/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,48 @@ impl<T, D> PubNubClientInstance<T, D> {
self.heartbeat().state(state)
}

/// Create a get state request builder.
///
/// This method is used to get state associated with `user_id` on
/// channels and and channels registered with channel groups.
///
/// Instance of [`SetStateRequestBuilder`] returned.
///
/// # Example
/// ```rust
/// use pubnub::presence::*;
/// # use pubnub::{Keyset, PubNubClientBuilder};
/// # use std::collections::HashMap;
///
/// #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use std::sync::Arc;
/// let mut pubnub = // PubNubClient
/// # PubNubClientBuilder::with_reqwest_transport()
/// # .with_keyset(Keyset {
/// # subscribe_key: "demo",
/// # publish_key: None,
/// # secret_key: None
/// # })
/// # .with_user_id("uuid")
/// # .build()?;
/// pubnub
/// .get_presence_state()
/// .channels(["lobby".into(), "announce".into()])
/// .channel_groups(["area-51".into()])
/// .execute()
/// .await?;
/// # Ok(())
/// # }
/// ```
pub fn get_presence_state(&self) -> GetStateRequestBuilder<T, D> {
GetStateRequestBuilder {
pubnub_client: Some(self.clone()),
user_id: Some(self.config.user_id.clone().to_string()),
..Default::default()
}
}

/// Create a here now request builder.
///
/// This method is used to get information about current occupancy of
Expand Down
Loading
Loading