Skip to content

Commit d578742

Browse files
parfeonXavrax
andauthored
Add subscribe request (#159)
feat(ee): add subscribe request Add the ability to construct and exec subscribe request with the following response parsing. feat: add emit messages / status effects feat: add event listeners Listeners implemented in form of a subscription object which allows polling on updates. test(contract-test): completed contract testing for subscribe Completed set of contract tests for subscription event engine. refactor(clippy): apply clippy suggestions fix: fix formatting warning --------- Co-authored-by: Xavrax <[email protected]>
1 parent 3de77c6 commit d578742

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+5966
-1163
lines changed

.github/workflows/run-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ jobs:
5151
token: ${{ secrets.GH_TOKEN }}
5252
features-path: tests/features
5353
- name: Run acceptance tests
54+
env:
55+
RUST_LOG: debug
5456
run: |
5557
cargo test --features contract_test --test contract_test
5658
- name: Expose acceptance tests reports

.github/workflows/run-validations.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,25 @@ jobs:
4646

4747
- name: Run cargo check tool to check if the code are valid
4848
run: |
49-
cargo check --workspace --all-targets --features="full"
49+
cargo check --workspace --all-targets --features="full"
5050
- name: Run cargo check tool to check if the raw domain code are valid
5151
run: |
52-
cargo check --workspace --no-default-features --features="pubnub_only"
52+
cargo check --workspace --no-default-features --features="pubnub_only"
5353
- name: Run cargo check tool to check if the `no_std` code are valid
5454
run: |
55-
cargo check --workspace --all-targets --no-default-features --features="full_no_std"
56-
55+
cargo check --workspace --all-targets --no-default-features --features="full_no_std"
5756
- name: Run cargo clippy tool to check if all the best code practices are followed
5857
run: |
59-
cargo clippy --workspace --all-targets --features="full" -- -D warnings
58+
cargo clippy --workspace --all-targets --features="full" -- -D warnings
6059
- name: Run cargo clippy tool to check if all the best code practices are followed for raw domain code
6160
run: |
62-
cargo clippy --workspace --no-default-features --features="pubnub_only" -- -D warnings
61+
cargo clippy --workspace --no-default-features --features="pubnub_only" -- -D warnings
6362
- name: Run cargo clippy tool to check if all the best code practices are followed for `no_std` code
6463
run: |
65-
cargo clippy --workspace --all-targets --no-default-features --features="full_no_std" -- -D warnings
66-
64+
cargo clippy --workspace --all-targets --no-default-features --features="full_no_std" -- -D warnings
6765
- name: Run cargo fmt tool to check if code are well formatted
6866
run: |
69-
cargo fmt --check --verbose --all
67+
cargo fmt --check --verbose --all
7068
7169
cargo-deny:
7270
name: Check Cargo crate dependencies

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ target
22
Cargo.lock
33
tests/features
44
tests/reports
5+
tests/logs
56

67
# GitHub Actions #
78
##################
89
.github/.release
10+
11+
# IDE
12+
.idea
13+
14+
# OS
15+
.DS_Store

Cargo.toml

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ build = "build.rs"
1414
[features]
1515

1616
# Enables all non-conflicting features
17-
full = ["publish", "access", "serde", "reqwest", "aescbc", "parse_token", "blocking", "std"]
17+
full = ["publish", "access", "serde", "reqwest", "aescbc", "parse_token", "blocking", "std", "tokio"]
1818

1919
# Enables all default features
20-
default = ["publish", "serde", "reqwest", "aescbc", "std", "blocking"]
20+
default = ["publish", "subscribe", "serde", "reqwest", "aescbc", "std", "blocking", "tokio"]
2121

2222
# [PubNub features]
2323

@@ -41,6 +41,9 @@ serde = ["dep:serde", "dep:serde_json", "hashbrown/serde"]
4141
## Enables reqwest implementation for transport layer
4242
reqwest = ["dep:reqwest", "dep:bytes"]
4343

44+
## Enables tokio runtime for subscribe loop
45+
tokio = ["dep:tokio"]
46+
4447
## Enables blocking implementation for transport layer
4548
blocking = ["reqwest?/blocking"]
4649

@@ -61,23 +64,25 @@ extra_platforms = ["spin/portable_atomic", "dep:portable-atomic"]
6164

6265
# [Internal features] (not intended for use outside of the library)
6366
contract_test = ["parse_token", "publish", "access"]
64-
full_no_std = ["serde", "reqwest", "aescbc", "parse_token", "blocking", "publish", "access", "subscribe"]
67+
full_no_std = ["serde", "reqwest", "aescbc", "parse_token", "blocking", "publish", "access", "subscribe", "tokio"]
6568
full_no_std_platform_independent = ["serde", "aescbc", "parse_token", "blocking", "publish", "access", "subscribe"]
6669
pubnub_only = ["aescbc", "parse_token", "blocking", "publish", "access", "subscribe"]
6770
mock_getrandom = ["getrandom/custom"]
6871
event_engine = []
6972
# TODO: temporary treated as internal until we officially release it
70-
subscribe = ["event_engine"]
73+
futures = ["dep:futures"]
74+
futures_tokio = ["dep:tokio"]
75+
subscribe = ["event_engine", "futures", "futures_tokio", "dep:async-channel"]
7176

7277
[dependencies]
7378
async-trait = "0.1"
7479
log = "0.4"
75-
hashbrown = "0.13"
80+
hashbrown = "0.14.0"
7681
spin = "0.9"
7782
phantom-type = { version = "0.4.2", default-features = false }
7883
percent-encoding = { version = "2.1", default-features = false }
7984
base64 = { version = "0.21", features = ["alloc"], default-features = false }
80-
derive_builder = {version = "0.12", default-features = false }
85+
derive_builder = { version = "0.12", default-features = false }
8186
uuid = { version = "1.3", features = ["v4"], default-features = false }
8287
snafu = { version = "0.7", features = ["rust_1_46"], default-features = false }
8388
rand = { version = "0.8.5", default-features = false }
@@ -93,7 +98,7 @@ serde_json = { version = "1.0", optional = true, features = ["alloc"] ,default-f
9398

9499
# reqwest
95100
reqwest = { version = "0.11", optional = true }
96-
bytes = {version = "1.4", default-features = false, optional = true }
101+
bytes = { version = "1.4", default-features = false, optional = true }
97102

98103
# crypto
99104
aes = { version = "0.8.2", optional = true }
@@ -103,6 +108,11 @@ getrandom = { version = "0.2", optional = true }
103108
# parse_token
104109
ciborium = { version = "0.2.1", default-features = false, optional = true }
105110

111+
# subscribe
112+
tokio = { version = "1", optional = true, features = ["rt-multi-thread", "macros", "time"] }
113+
futures = { version = "0.3.28", optional = true }
114+
async-channel = { version = "1.8", optional = true }
115+
106116
# extra_platforms
107117
portable-atomic = { version = "1.3", optional = true, default-features = false, features = ["require-cas", "critical-section"] }
108118

@@ -111,14 +121,13 @@ getrandom = { version = "0.2", features = ["js"] }
111121

112122
[dev-dependencies]
113123
async-trait = "0.1"
114-
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
124+
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
115125
wiremock = "0.5"
116126
env_logger = "0.10"
117-
cucumber = { version = "0.19", features = ["output-junit"] }
118-
futures = "0.3"
127+
cucumber = { version = "0.20.0", features = ["output-junit"] }
119128
reqwest = { version = "0.11", features = ["json"] }
120129
test-case = "3.0"
121-
hashbrown = { version = "0.13", features = ["serde"] }
130+
hashbrown = { version = "0.14.0", features = ["serde"] }
122131
getrandom = { version = "0.2", features = ["custom"] }
123132

124133
[build-dependencies]
@@ -152,3 +161,7 @@ required-features = ["default", "blocking", "access"]
152161
name = "custom_origin"
153162
required-features = ["default"]
154163

164+
[[example]]
165+
name = "subscribe"
166+
required-features = ["default", "subscribe"]
167+

examples/subscribe.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use futures::StreamExt;
2+
use pubnub::dx::subscribe::{SubscribeStreamEvent, Update};
3+
use pubnub::{Keyset, PubNubClientBuilder};
4+
use serde::Deserialize;
5+
use std::env;
6+
7+
#[derive(Debug, Deserialize)]
8+
struct Message {
9+
// Allowing dead code because we don't use these fields
10+
// in this example.
11+
#[allow(dead_code)]
12+
url: String,
13+
#[allow(dead_code)]
14+
description: String,
15+
}
16+
17+
#[tokio::main]
18+
async fn main() -> Result<(), Box<dyn snafu::Error>> {
19+
let publish_key = env::var("SDK_PUB_KEY")?;
20+
let subscribe_key = env::var("SDK_SUB_KEY")?;
21+
22+
let client = PubNubClientBuilder::with_reqwest_transport()
23+
.with_keyset(Keyset {
24+
subscribe_key,
25+
publish_key: Some(publish_key),
26+
secret_key: None,
27+
})
28+
.with_user_id("user_id")
29+
.build()?;
30+
31+
println!("running!");
32+
33+
let subscription = client
34+
.subscribe()
35+
.channels(["my_channel".into(), "other_channel".into()].to_vec())
36+
.heartbeat(10)
37+
.filter_expression("some_filter")
38+
.execute()?;
39+
40+
tokio::spawn(subscription.stream().for_each(|event| async move {
41+
match event {
42+
SubscribeStreamEvent::Update(update) => {
43+
println!("\nupdate: {:?}", update);
44+
match update {
45+
Update::Message(message) | Update::Signal(message) => {
46+
// Deserialize the message payload as you wish
47+
match serde_json::from_slice::<Message>(&message.data) {
48+
Ok(message) => println!("defined message: {:?}", message),
49+
Err(_) => {
50+
println!("other message: {:?}", String::from_utf8(message.data))
51+
}
52+
}
53+
}
54+
Update::Presence(presence) => {
55+
println!("presence: {:?}", presence)
56+
}
57+
Update::Object(object) => {
58+
println!("object: {:?}", object)
59+
}
60+
Update::MessageAction(action) => {
61+
println!("message action: {:?}", action)
62+
}
63+
Update::File(file) => {
64+
println!("file: {:?}", file)
65+
}
66+
}
67+
}
68+
SubscribeStreamEvent::Status(status) => println!("\nstatus: {:?}", status),
69+
}
70+
}));
71+
72+
// Sleep for a minute. Now you can send messages to the channels
73+
// "my_channel" and "other_channel" and see them printed in the console.
74+
// You can use the publish example or [PubNub console](https://www.pubnub.com/docs/console/)
75+
// to send messages.
76+
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
77+
78+
// You can also cancel the subscription at any time.
79+
subscription.unsubscribe().await;
80+
81+
Ok(())
82+
}

src/core/cryptor.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! encryption and decryption of published data.
55
66
use crate::core::error::PubNubError;
7-
use crate::lib::alloc::vec::Vec;
7+
use crate::lib::{alloc::vec::Vec, core::fmt::Debug};
88

99
/// This trait is used to encrypt and decrypt messages sent to the
1010
/// [`PubNub API`].
@@ -24,47 +24,36 @@ use crate::lib::alloc::vec::Vec;
2424
/// ```
2525
/// use pubnub::core::{Cryptor, error::PubNubError};
2626
///
27+
/// #[derive(Debug)]
2728
/// struct MyCryptor;
2829
///
2930
/// impl Cryptor for MyCryptor {
30-
/// fn encrypt<'en, T>(&self, source: T) -> Result<Vec<u8>, PubNubError>
31-
/// where
32-
/// T: Into<&'en [u8]>
33-
/// {
31+
/// fn encrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError> {
3432
/// // Encrypt provided data here
35-
///
3633
/// Ok(vec![])
3734
/// }
3835
///
39-
/// fn decrypt<'de, T>(&self, source: T) -> Result<Vec<u8>, PubNubError>
40-
/// where
41-
/// T: Into<&'de [u8]>
42-
/// {
36+
/// fn decrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError> {
4337
/// // Decrypt provided data here
44-
///
4538
/// Ok(vec![])
4639
/// }
4740
/// }
4841
/// ```
4942
///
5043
/// [`dx`]: ../dx/index.html
5144
/// [`PubNub API`]: https://www.pubnub.com/docs
52-
pub trait Cryptor {
45+
pub trait Cryptor: Debug + Send + Sync {
5346
/// Decrypt provided data.
5447
///
5548
/// # Errors
5649
/// Should return an [`PubNubError::Encryption`] if provided data can't
5750
/// be encrypted or underlying cryptor misconfigured.
58-
fn encrypt<'en, T>(&self, source: T) -> Result<Vec<u8>, PubNubError>
59-
where
60-
T: Into<&'en [u8]>;
51+
fn encrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError>;
6152

6253
/// Decrypt provided data.
6354
///
6455
/// # Errors
6556
/// Should return an [`PubNubError::Decryption`] if provided data can't
6657
/// be decrypted or underlying cryptor misconfigured.
67-
fn decrypt<'de, T>(&self, source: T) -> Result<Vec<u8>, PubNubError>
68-
where
69-
T: Into<&'de [u8]>;
58+
fn decrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError>;
7059
}

src/core/deserialize.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! Deserialization module
2+
//!
3+
//! This module provides a [`Deserialize`] trait for the Pubnub protocol.
4+
//!
5+
//! You can implement this trait for your own types, or use one of the provided
6+
//! features to use a deserialization library.
7+
//!
8+
//! [`Deserialize`]: trait.Deserialize.html
9+
10+
use crate::core::PubNubError;
11+
12+
/// Deserialize values
13+
///
14+
/// This trait provides a [`deserialize`] method for the Pubnub protocol.
15+
///
16+
/// You can implement this trait for your own types, or use the provided
17+
/// implementations for [`Into<Vec<u8>>`].
18+
///
19+
/// [`deserialize`]: #tymethod.deserialize
20+
pub trait Deserialize<'de>: Send + Sync {
21+
/// Type to which binary data should be mapped.
22+
type Type;
23+
24+
/// Deserialize the value
25+
fn deserialize(bytes: &'de [u8]) -> Result<Self::Type, PubNubError>;
26+
}

src/core/deserializer.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! This module contains the `Deserialize` trait which is used to implement
44
//! deserialization of Rust data structures.
55
6-
use super::PubNubError;
6+
use crate::core::PubNubError;
77

88
/// Trait for deserializing Rust data structures.
99
///
@@ -30,8 +30,8 @@ use super::PubNubError;
3030
///
3131
/// struct MyDeserializer;
3232
///
33-
/// impl<'de> Deserializer<'de, PublishResult> for MyDeserializer {
34-
/// fn deserialize(&self, bytes: &'de [u8]) -> Result<PublishResult, PubNubError> {
33+
/// impl Deserializer<PublishResult> for MyDeserializer {
34+
/// fn deserialize(&self, bytes: &[u8]) -> Result<PublishResult, PubNubError> {
3535
/// // ...
3636
/// # unimplemented!()
3737
/// }
@@ -42,14 +42,14 @@ use super::PubNubError;
4242
/// [`PublishResponseBody`]: ../../dx/publish/result/enum.PublishResponseBody.html
4343
/// [`GrantTokenResponseBody`]: ../../dx/access/result/enum.GrantTokenResponseBody.html
4444
/// [`RevokeTokenResponseBody`]: ../../dx/access/result/enum.RevokeTokenResponseBody.html
45-
pub trait Deserializer<'de, T> {
46-
/// Deserialize a `&[u8]` into a `Result<T, PubNubError>`.
45+
pub trait Deserializer<T>: Send + Sync {
46+
/// Deserialize a `&Vec<u8>` into a `Result<T, PubNubError>`.
4747
///
4848
/// # Errors
4949
///
5050
/// This method should return [`PubNubError::DeserializationError`] if the
5151
/// deserialization fails.
5252
///
5353
/// [`PubNubError::DeserializationError`]: ../enum.PubNubError.html#variant.DeserializationError
54-
fn deserialize(&self, bytes: &'de [u8]) -> Result<T, PubNubError>;
54+
fn deserialize(&self, bytes: &[u8]) -> Result<T, PubNubError>;
5555
}

0 commit comments

Comments
 (0)