Skip to content

Commit 4d6c07f

Browse files
authored
where now presence API (#169)
1 parent bb4b449 commit 4d6c07f

File tree

6 files changed

+317
-15
lines changed

6 files changed

+317
-15
lines changed

examples/where_now.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
66
let publish_key = env::var("SDK_PUB_KEY")?;
77
let subscribe_key = env::var("SDK_SUB_KEY")?;
88

9-
let _client = PubNubClientBuilder::with_reqwest_transport()
9+
let client = PubNubClientBuilder::with_reqwest_transport()
1010
.with_keyset(Keyset {
1111
subscribe_key,
1212
publish_key: Some(publish_key),
@@ -17,13 +17,9 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
1717

1818
println!("running!");
1919

20-
// let where_user = client
21-
// .where_now()
22-
// .user_id("user_id")
23-
// .execute()
24-
// .await?;
25-
//
26-
// println!("All channels data: {:?}", where_user);
20+
let where_user = client.where_now().user_id("user_id").execute().await?;
21+
22+
println!("User channels: {:?}", where_user);
2723

2824
Ok(())
2925
}

examples/where_now_blocking.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ fn main() -> Result<(), Box<dyn snafu::Error>> {
55
let publish_key = env::var("SDK_PUB_KEY")?;
66
let subscribe_key = env::var("SDK_SUB_KEY")?;
77

8-
let _client = PubNubClientBuilder::with_reqwest_transport()
8+
let client = PubNubClientBuilder::with_reqwest_blocking_transport()
99
.with_keyset(Keyset {
1010
subscribe_key,
1111
publish_key: Some(publish_key),
@@ -16,12 +16,9 @@ fn main() -> Result<(), Box<dyn snafu::Error>> {
1616

1717
println!("running!");
1818

19-
// let where_user = client
20-
// .where_now()
21-
// .user_id("user_id")
22-
// .execute_blocking()?;
23-
//
24-
// println!("All channels data: {:?}", where_user);
19+
let where_user = client.where_now().user_id("user_id").execute_blocking()?;
20+
21+
println!("User channels: {:?}", where_user);
2522

2623
Ok(())
2724
}

src/dx/presence/builders/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ pub(crate) mod leave;
2121
pub(crate) use here_now::HereNowRequestBuilder;
2222
pub(crate) mod here_now;
2323

24+
#[doc(inline)]
25+
pub(crate) use where_now::WhereNowRequestBuilder;
26+
pub(crate) mod where_now;
27+
2428
use crate::{dx::pubnub_client::PubNubClientInstance, lib::alloc::string::String};
2529

2630
/// Validate [`PubNubClient`] configuration.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! PubNub Where Now module.
2+
//!
3+
//! The [`WhereNowRequestBuilder`] lets you make and execute a Here Now request
4+
//! that will associate a user with a channel.
5+
6+
use derive_builder::Builder;
7+
8+
use crate::{
9+
core::{
10+
utils::{
11+
encoding::{url_encode_extended, UrlEncodeExtension},
12+
headers::{APPLICATION_JSON, CONTENT_TYPE},
13+
},
14+
Deserializer, PubNubError, Transport, TransportMethod, TransportRequest,
15+
},
16+
dx::{presence::builders, pubnub_client::PubNubClientInstance},
17+
lib::collections::HashMap,
18+
presence::result::{WhereNowResponseBody, WhereNowResult},
19+
};
20+
21+
/// The Here Now request builder.
22+
///
23+
/// Allows you to build a Here Now request that is sent to the [`PubNub`] network.
24+
///
25+
/// This struct is used by the [`here_now`] method of the [`PubNubClient`].
26+
/// The [`here_now`] method is used to acquire information about the current
27+
/// state of a channel.
28+
///
29+
/// [`PubNub`]: https://www.pubnub.com/
30+
#[derive(Builder, Debug)]
31+
#[builder(
32+
pattern = "owned",
33+
build_fn(vis = "pub(in crate::dx::presence)", validate = "Self::validate"),
34+
no_std
35+
)]
36+
pub struct WhereNowRequest<T, D> {
37+
/// Current client which can provide transportation to perform the request.
38+
///
39+
/// This field is used to get [`Transport`] to perform the request.
40+
#[builder(field(vis = "pub(in crate::dx::presence)"), setter(custom))]
41+
pub(in crate::dx::presence) pubnub_client: PubNubClientInstance<T, D>,
42+
43+
#[builder(
44+
field(vis = "pub(in crate::dx::presence)"),
45+
setter(strip_option, into),
46+
default
47+
)]
48+
/// Identifier for which `state` should be associated for provided list of
49+
/// channels and groups.
50+
pub(in crate::dx::presence) user_id: String,
51+
}
52+
53+
impl<T, D> WhereNowRequestBuilder<T, D> {
54+
/// Validate user-provided data for request builder.
55+
///
56+
/// Validator ensure that list of provided data is enough to build valid
57+
/// set state request instance.
58+
fn validate(&self) -> Result<(), String> {
59+
builders::validate_configuration(&self.pubnub_client)
60+
}
61+
62+
/// Build [`SetStateRequest`] from builder.
63+
fn request(self) -> Result<WhereNowRequest<T, D>, PubNubError> {
64+
self.build()
65+
.map_err(|err| PubNubError::general_api_error(err.to_string(), None, None))
66+
}
67+
}
68+
69+
impl<T, D> WhereNowRequest<T, D> {
70+
/// Create transport request from the request builder.
71+
pub(in crate::dx::presence) fn transport_request(
72+
&self,
73+
) -> Result<TransportRequest, PubNubError> {
74+
let sub_key = &self.pubnub_client.config.subscribe_key;
75+
76+
let user_id = if self.user_id.is_empty() {
77+
&*self.pubnub_client.config.user_id
78+
} else {
79+
&self.user_id
80+
};
81+
82+
Ok(TransportRequest {
83+
path: format!(
84+
"/v2/presence/sub-key/{sub_key}/uuid/{}",
85+
url_encode_extended(user_id.as_bytes(), UrlEncodeExtension::NonChannelPath)
86+
),
87+
query_parameters: HashMap::new(),
88+
method: TransportMethod::Get,
89+
headers: [(CONTENT_TYPE.into(), APPLICATION_JSON.into())].into(),
90+
body: None,
91+
})
92+
}
93+
}
94+
95+
impl<T, D> WhereNowRequestBuilder<T, D>
96+
where
97+
T: Transport,
98+
D: Deserializer + 'static,
99+
{
100+
/// Build and call asynchronous request.
101+
pub async fn execute(self) -> Result<WhereNowResult, PubNubError> {
102+
let request = self.request()?;
103+
let transport_request = request.transport_request()?;
104+
let client = request.pubnub_client.clone();
105+
let deserializer = client.deserializer.clone();
106+
107+
transport_request
108+
.send::<WhereNowResponseBody, _, _, _>(&client.transport, deserializer)
109+
.await
110+
}
111+
}
112+
113+
#[cfg(feature = "blocking")]
114+
impl<T, D> WhereNowRequestBuilder<T, D>
115+
where
116+
T: crate::core::blocking::Transport,
117+
D: Deserializer + 'static,
118+
{
119+
/// Build and call synchronous request.
120+
pub fn execute_blocking(self) -> Result<WhereNowResult, PubNubError> {
121+
let request = self.request()?;
122+
let transport_request = request.transport_request()?;
123+
let client = request.pubnub_client.clone();
124+
let deserializer = client.deserializer.clone();
125+
transport_request
126+
.send_blocking::<WhereNowResponseBody, _, _, _>(&client.transport, deserializer)
127+
}
128+
}

src/dx/presence/mod.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,46 @@ impl<T, D> PubNubClientInstance<T, D> {
213213
..Default::default()
214214
}
215215
}
216+
217+
/// Create a where now request builder.
218+
///
219+
/// This method is used to get information about channels where `user_id`
220+
/// is currently present.
221+
///
222+
/// Instance of [`WhereNowRequestBuilder`] returned.
223+
///
224+
/// # Example
225+
/// ```rust
226+
/// use pubnub::presence::*;
227+
/// # use pubnub::{Keyset, PubNubClientBuilder};
228+
/// # use std::collections::HashMap;
229+
///
230+
/// # #[tokio::main]
231+
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
232+
/// # use std::sync::Arc;
233+
/// let mut pubnub = // PubNubClient
234+
/// # PubNubClientBuilder::with_reqwest_transport()
235+
/// # .with_keyset(Keyset {
236+
/// # subscribe_key: "demo",
237+
/// # publish_key: None,
238+
/// # secret_key: None,
239+
/// # })
240+
/// # .with_user_id("uuid")
241+
/// # .build()?;
242+
/// let response = pubnub.where_now().user_id("user_id").execute().await?;
243+
244+
///
245+
/// println!("User channels: {:?}", response);
246+
///
247+
/// # Ok(())
248+
/// # }
249+
/// ```
250+
pub fn where_now(&self) -> WhereNowRequestBuilder<T, D> {
251+
WhereNowRequestBuilder {
252+
pubnub_client: Some(self.clone()),
253+
..Default::default()
254+
}
255+
}
216256
}
217257

218258
impl<T, D> PubNubClientInstance<T, D>

src/dx/presence/result.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,104 @@ impl Deref for HereNowResult {
629629
}
630630
}
631631

632+
/// The result of a here now operation.
633+
#[derive(Debug, Clone, PartialEq, Eq)]
634+
pub struct WhereNowResult {
635+
/// Here now channels.
636+
pub channels: Vec<String>,
637+
}
638+
639+
/// Where now service response body for where now.
640+
/// This is a success response body for a where now operation in The
641+
/// Presence service.
642+
///
643+
/// It contains information about the success of the operation, the service that
644+
/// provided the response, and the result of the operation.
645+
///
646+
/// It also contains information about the channels that the user is currently
647+
/// subscribed to.
648+
///
649+
/// Additionally, it can provide error information if the operation failed.
650+
#[derive(Debug, Clone, PartialEq, Eq)]
651+
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
652+
pub enum WhereNowResponseBody {
653+
/// This is a success response body for a where now operation in the
654+
/// Presence service.
655+
///
656+
/// It contains information about the success of the operation, the service
657+
/// that provided the response, and the result of the operation.
658+
SuccessResponse(APISuccessBodyWithPayload<WhereNowResponseSuccessBody>),
659+
660+
/// This is an error response body for a where now operation in the Presence
661+
/// service.
662+
///
663+
/// It contains information about the service that provided the response and
664+
/// details of what exactly was wrong.
665+
///
666+
/// # Example
667+
/// ```json
668+
/// {
669+
/// "error": {
670+
/// "message": "Invalid signature",
671+
/// "source": "grant",
672+
/// "details": [
673+
/// {
674+
/// "message": "Client and server produced different signatures for the same inputs.",
675+
/// "location": "signature",
676+
/// "locationType": "query"
677+
/// }
678+
/// ]
679+
/// },
680+
/// "service": "Access Manager",
681+
/// "status": 403
682+
/// }
683+
/// ```
684+
ErrorResponse(APIErrorBody),
685+
}
686+
687+
/// The result of a where now operation.
688+
///
689+
/// # Example
690+
/// ```json
691+
/// {
692+
/// "status":200,
693+
/// "message":"OK",
694+
/// "payload":{
695+
/// "channels":[
696+
/// "my_channel"
697+
/// ]
698+
/// },
699+
/// "service":"Presence"
700+
/// }
701+
/// ```
702+
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
703+
#[derive(Debug, Clone, PartialEq, Eq)]
704+
pub struct WhereNowResponseSuccessBody {
705+
/// Channels that the user is currently subscribed to.
706+
pub channels: Vec<String>,
707+
}
708+
709+
impl TryFrom<WhereNowResponseBody> for WhereNowResult {
710+
type Error = PubNubError;
711+
712+
fn try_from(value: WhereNowResponseBody) -> Result<Self, Self::Error> {
713+
match value {
714+
WhereNowResponseBody::SuccessResponse(resp) => Ok(Self {
715+
channels: resp.payload.channels,
716+
}),
717+
WhereNowResponseBody::ErrorResponse(resp) => Err(resp.into()),
718+
}
719+
}
720+
}
721+
722+
impl Deref for WhereNowResult {
723+
type Target = Vec<String>;
724+
725+
fn deref(&self) -> &Self::Target {
726+
&self.channels
727+
}
728+
}
729+
632730
#[cfg(test)]
633731
mod it_should {
634732
use std::collections::HashMap;
@@ -1009,4 +1107,43 @@ mod it_should {
10091107

10101108
assert!(result.is_err());
10111109
}
1110+
1111+
#[test]
1112+
fn parse_where_now_response() {
1113+
use serde_json::json;
1114+
1115+
let input = json!({
1116+
"status":200,
1117+
"message":"OK",
1118+
"payload":{
1119+
"channels":[
1120+
"my_channel"
1121+
]
1122+
},
1123+
"service":"Presence"
1124+
});
1125+
1126+
let result: WhereNowResult = serde_json::from_value::<WhereNowResponseBody>(input)
1127+
.unwrap()
1128+
.try_into()
1129+
.unwrap();
1130+
1131+
result
1132+
.channels
1133+
.iter()
1134+
.any(|channel| channel == "my_channel");
1135+
}
1136+
1137+
#[test]
1138+
fn parse_where_now_error_response() {
1139+
let body = WhereNowResponseBody::ErrorResponse(APIErrorBody::AsObjectWithService {
1140+
status: 400,
1141+
error: true,
1142+
service: "service".into(),
1143+
message: "error".into(),
1144+
});
1145+
let result: Result<WhereNowResult, PubNubError> = body.try_into();
1146+
1147+
assert!(result.is_err());
1148+
}
10121149
}

0 commit comments

Comments
 (0)