diff --git a/.github/workflows/commands-handler.yml b/.github/workflows/commands-handler.yml index 3f8bce55..a123555c 100644 --- a/.github/workflows/commands-handler.yml +++ b/.github/workflows/commands-handler.yml @@ -24,12 +24,12 @@ jobs: run: echo -e "\033[38;2;19;181;255mThis is regular commit which should be ignored.\033[0m" - name: Checkout repository if: steps.user-check.outputs.expected-user == 'true' - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: token: ${{ secrets.GH_TOKEN }} - name: Checkout release actions if: steps.user-check.outputs.expected-user == 'true' - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: repository: pubnub/client-engineering-deployment-tools ref: v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index edfda40f..7c277121 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: release: ${{ steps.check.outputs.ready }} steps: - name: Checkout actions - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: repository: pubnub/client-engineering-deployment-tools ref: v1 @@ -34,13 +34,13 @@ jobs: if: needs.check-release.outputs.release == 'true' steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: # This should be the same as the one specified for on.pull_request.branches ref: master token: ${{ secrets.GH_TOKEN }} - name: Checkout actions - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: repository: pubnub/client-engineering-deployment-tools ref: v1 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c9f17242..a8d492c3 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -23,7 +23,7 @@ jobs: group: organization/Default steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Run unit tests run: | cargo test --features="full" @@ -39,9 +39,9 @@ jobs: group: organization/Default steps: - name: Checkout project - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Checkout mock-server action - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: repository: pubnub/client-engineering-deployment-tools ref: v1 @@ -58,7 +58,7 @@ jobs: run: | cargo test --features contract_test --test contract_test - name: Expose acceptance tests reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: acceptance-test-reports path: tests/reports/*.xml diff --git a/.github/workflows/run-validations.yml b/.github/workflows/run-validations.yml index 44c2bff9..c6d282cb 100644 --- a/.github/workflows/run-validations.yml +++ b/.github/workflows/run-validations.yml @@ -44,7 +44,7 @@ jobs: runs-on: group: organization/Default steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Run cargo check tool to check if the code are valid run: | @@ -80,8 +80,8 @@ jobs: # Prevent sudden announcement of a new advisory from failing ci: continue-on-error: ${{ matrix.checks == 'advisories' }} steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: actions/checkout@v5 + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check ${{ matrix.checks }} @@ -90,7 +90,7 @@ jobs: runs-on: group: organization/Default steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Install WASM target uses: actions-rs/toolchain@v1 @@ -109,7 +109,7 @@ jobs: runs-on: group: organization/Default steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Install `no_std` target uses: actions-rs/toolchain@v1 @@ -119,12 +119,16 @@ jobs: - name: Run cargo check tool to check if the `no_std` code are valid uses: actions-rs/cargo@v1 + env: + RUSTFLAGS: '--cfg getrandom_backend="unsupported"' with: command: check - args: --lib --no-default-features --features=full_no_std_platform_independent,mock_getrandom + args: --lib --no-default-features --features=full_no_std_platform_independent - name: Run cargo check tool to check if the additional example code are valid uses: actions-rs/cargo@v1 + env: + RUSTFLAGS: '--cfg getrandom_backend="custom"' with: command: check args: --manifest-path examples/no_std/Cargo.toml --target thumbv7m-none-eabi diff --git a/.pubnub.yml b/.pubnub.yml index e96f2e8d..5b80d062 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,24 @@ name: rust -version: 0.6.0 +version: 0.7.0 schema: 1 scm: github.com/pubnub/rust files: [] changelog: + - date: 2025-10-28 + version: 0.7.0 + changes: + - type: feature + text: "Add `limit` (default `1000`) and `offset` parameters for `here_now` to fetch presence in portions." + - type: feature + text: "Use default retry configuration for `subscribe` and `presence` REST API calls." + - type: bug + text: "Fix the issue because of which it was possible to add the same channel / group multiple times to the `subscribe`, `heartbeat`, or `leave` requests." + - type: bug + text: "Fix the `Presence` enum variant to not intercept user-published messages that partly match the `Presence` variant." + - type: improvement + text: "Synchronize subscription flow with other SDKs." + - type: improvement + text: "Synchronize presence flow with other SDKs." - date: 2024-02-07 version: 0.6.0 changes: diff --git a/Cargo.toml b/Cargo.toml index 7739d7ca..ee47bfa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pubnub" -version = "0.6.0" +version = "0.7.0" edition = "2021" license-file = "LICENSE" authors = ["PubNub "] @@ -67,7 +67,6 @@ contract_test = ["parse_token", "publish", "access", "crypto", "std", "subscribe full_no_std = ["serde", "reqwest", "crypto", "parse_token", "blocking", "publish", "access", "subscribe", "tokio", "presence"] full_no_std_platform_independent = ["serde", "crypto", "parse_token", "blocking", "publish", "access", "subscribe", "presence"] pubnub_only = ["crypto", "parse_token", "blocking", "publish", "access", "subscribe", "presence"] -mock_getrandom = ["getrandom/custom"] # TODO: temporary treated as internal until we officially release it subscribe = ["dep:futures"] presence = ["dep:futures"] @@ -80,28 +79,28 @@ spin = "0.9" phantom-type = { version = "0.4.2", default-features = false } percent-encoding = { version = "2.1", default-features = false } base64 = { version = "0.21", features = ["alloc"], default-features = false } -derive_builder = { version = "0.12", default-features = false } +derive_builder = { version = "0.12.0", default-features = false } uuid = { version = "1.3", features = ["v4"], default-features = false } snafu = { version = "0.7", features = ["rust_1_46"], default-features = false } rand = { version = "0.8.5", default-features = false } # signature hmac = "0.12" -sha2 = {version = "0.10", default-features = false } +sha2 = { version = "0.10", default-features = false } time = { version = "0.3", features = ["alloc"], default-features = false } # serde -serde = { version = "1.0", features = ["derive"], optional = true, default-features = false } -serde_json = { version = "1.0", optional = true, features = ["alloc"] ,default-features = false } +serde = { version = "1.0", features = ["derive", "alloc"], optional = true, default-features = false } +serde_json = { version = "1.0", optional = true, features = ["alloc"], default-features = false } # reqwest -reqwest = { version = "0.11", optional = true } +reqwest = { version = "0.12", optional = true } bytes = { version = "1.4", default-features = false, optional = true } # crypto aes = { version = "0.8.2", optional = true } cbc = { version = "0.1.2", optional = true } -getrandom = { version = "0.2", optional = true } +getrandom = { version = "0.3", optional = true } # parse_token ciborium = { version = "0.2.1", default-features = false, optional = true } @@ -115,7 +114,7 @@ async-channel = { version = "1.8", optional = true } portable-atomic = { version = "1.3", optional = true, default-features = false, features = ["require-cas", "critical-section"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2", features = ["js"] } +getrandom = { version = "0.3", features = ["wasm_js"] } [dev-dependencies] async-trait = "0.1" @@ -123,10 +122,10 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } wiremock = "0.5" env_logger = "0.10" cucumber = { version = "0.20.2", features = ["output-junit"] } -reqwest = { version = "0.11", features = ["json"] } +reqwest = { version = "0.12", features = ["json"] } test-case = "3.0" hashbrown = { version = "0.14.0", features = ["serde"] } -getrandom = { version = "0.2", features = ["custom"] } +getrandom = { version = "0.3" } [build-dependencies] built = "0.6" diff --git a/README.md b/README.md index c125e259..55588cfb 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ Add `pubnub` to your Rust project in the `Cargo.toml` file: ```toml # default features [dependencies] -pubnub = "0.6.0" +pubnub = "0.7.0" # all features [dependencies] -pubnub = { version = "0.6.0", features = ["full"] } +pubnub = { version = "0.7.0", features = ["full"] } ``` ### Example @@ -164,11 +164,11 @@ disable them in the `Cargo.toml` file, like so: ```toml # only blocking and access + default features [dependencies] -pubnub = { version = "0.6.0", features = ["blocking", "access"] } +pubnub = { version = "0.7.0", features = ["blocking", "access"] } # only parse_token + default features [dependencies] -pubnub = { version = "0.6.0", features = ["parse_token"] } +pubnub = { version = "0.7.0", features = ["parse_token"] } ``` ### Available features @@ -207,7 +207,7 @@ you need, for example: ```toml [dependencies] -pubnub = { version = "0.6.0", default-features = false, features = ["serde", "publish", +pubnub = { version = "0.7.0", default-features = false, features = ["serde", "publish", "blocking"] } ``` diff --git a/deny.toml b/deny.toml index f8ba46a9..1f959dac 100644 --- a/deny.toml +++ b/deny.toml @@ -1,21 +1,22 @@ -# Root options +[graph] targets = [ #{ triple = "x86_64-unknown-linux-musl" }, ] all-features = false no-default-features = false + +[output] feature-depth = 1 # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] +version = 2 db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] -vulnerability = "deny" -unmaintained = "warn" +unmaintained = "workspace" yanked = "warn" -notice = "warn" ignore = [ #"RUSTSEC-0000-0000", ] @@ -24,7 +25,7 @@ ignore = [ # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -unlicensed = "deny" +version = 2 allow = [ # Before inserting a new license here, please check if it is already in the list of # licenses that are allowed: https://blueoakcouncil.org/list @@ -33,15 +34,10 @@ allow = [ "Apache-2.0", "BlueOak-1.0.0", "Unicode-DFS-2016", + "Unicode-3.0", "BSD-3-Clause", #"Apache-2.0 WITH LLVM-exception", ] -deny = [ - #"Nokia", -] -copyleft = "warn" -allow-osi-fsf-free = "neither" -default = "deny" confidence-threshold = 0.8 exceptions = [ { allow = ["LicenseRef-PubNub-Software-Development-Kit-License"], name = "pubnub" }, @@ -52,7 +48,7 @@ exceptions = [ [[licenses.clarify]] name = "pubnub" expression = "LicenseRef-PubNub-Software-Development-Kit-License" -license-files = [ { path = "LICENSE", hash = 0x48826f13 }, ] +license-files = [{ path = "LICENSE", hash = 0x48826f13 }, ] [licenses.private] diff --git a/examples/no_std/Cargo.toml b/examples/no_std/Cargo.toml index 28a5e316..4a29c15c 100644 --- a/examples/no_std/Cargo.toml +++ b/examples/no_std/Cargo.toml @@ -6,9 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -pubnub = { path = "../../", default_features = false, features = ["blocking", "serde", "publish", "subscribe", "presence"] } -serde = { version = "1.0", default_features = false, features = ["derive"] } -getrandom = { version = "0.2", default_features = false, features = ["custom"] } +pubnub = { path = "../../", default-features = false, features = ["blocking", "serde", "publish", "subscribe", "presence"] } +serde = { version = "1.0", default-features = false, features = ["derive"] } +getrandom = { version = "0.3", default_features = false } [[bin]] name = "publish" diff --git a/examples/no_std/src/here_now.rs b/examples/no_std/src/here_now.rs index 0750b8d9..c998f670 100644 --- a/examples/no_std/src/here_now.rs +++ b/examples/no_std/src/here_now.rs @@ -25,11 +25,11 @@ use pubnub::{ // As getrandom crate has limited support of targets, we need to provide custom // implementation of `getrandom` function. -getrandom::register_custom_getrandom!(custom_random); fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { // We're using `42` as a random number, because it's the answer // to the Ultimate Question of Life, the Universe, and Everything. - // In your program, you should use proper random number generator that is supported by your target. + // In your program, you should use proper random number generator that is + // supported by your target. for i in buf.iter_mut() { *i = 42; } @@ -37,9 +37,27 @@ fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { Ok(()) } +// This function is used to register the custom implementation of `getrandom` +// function. +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + #[allow(unsafe_code)] + let buf = unsafe { + // fill the buffer with zeros + core::ptr::write_bytes(dest, 0, len); + // create mutable byte slice + core::slice::from_raw_parts_mut(dest, len) + }; + custom_random(buf) +} + // Many targets have very specific requirements for networking, so it's hard to // provide a generic implementation. -// Depending on the target, you will probably need to implement `Transport` trait. +// Depending on the target, you will probably need to implement `Transport` +// trait. struct MyTransport; impl Transport for MyTransport { @@ -55,8 +73,8 @@ impl Transport for MyTransport { // As our target does not have `std` library, we need to provide custom // implementation of `GlobalAlloc` trait. // -// In your program, you should use proper allocator that is supported by your target. -// Here you have dummy implementation that does nothing. +// In your program, you should use proper allocator that is supported by your +// target. Here you have dummy implementation that does nothing. #[derive(Default)] pub struct Allocator; @@ -73,23 +91,23 @@ static GLOBAL_ALLOCATOR: Allocator = Allocator; // As our target does not have `std` library, we need to provide custom // implementation of `panic_handler`. // -// In your program, you should use proper panic handler that is supported by your target. -// Here you have dummy implementation that does nothing. +// In your program, you should use proper panic handler that is supported by +// your target. Here you have dummy implementation that does nothing. #[panic_handler] fn panicking(_: &PanicInfo) -> ! { loop {} } -// As we're using `no_main` attribute, we need to define `main` function manually. -// For this example we're using `extern "C"` ABI to make it work. +// As we're using `no_main` attribute, we need to define `main` function +// manually. For this example we're using `extern "C"` ABI to make it work. #[no_mangle] pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> usize { publish_example().map(|_| 0).unwrap() } -// In standard subscribe examples we use `println` macro to print the result of the operation -// and it shows the idea of the example. `no_std` does not support `println` macro, -// so we're using `do_a_thing` function instead. +// In standard subscribe examples we use `println` macro to print the result of +// the operation and it shows the idea of the example. `no_std` does not support +// `println` macro, so we're using `do_a_thing` function instead. fn do_a_thing(_: &T) {} // As `no_std` does not support `Error` trait, we use `PubNubError` instead. @@ -115,8 +133,8 @@ fn publish_example() -> Result<(), PubNubError> { .include_user_id(true) .execute_blocking()?; - // As `no_std` does not support `println` macro, we're using `do_a_thing` function instead - // to show possible usage of the result. + // As `no_std` does not support `println` macro, we're using `do_a_thing` + // function instead to show possible usage of the result. channels_now.iter().for_each(|channel| { do_a_thing(&channel.name); diff --git a/examples/no_std/src/presence_state.rs b/examples/no_std/src/presence_state.rs index b2129252..dc312e6a 100644 --- a/examples/no_std/src/presence_state.rs +++ b/examples/no_std/src/presence_state.rs @@ -32,11 +32,11 @@ struct State { // As getrandom crate has limited support of targets, we need to provide custom // implementation of `getrandom` function. -getrandom::register_custom_getrandom!(custom_random); fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { // We're using `42` as a random number, because it's the answer // to the Ultimate Question of Life, the Universe, and Everything. - // In your program, you should use proper random number generator that is supported by your target. + // In your program, you should use proper random number generator that is + // supported by your target. for i in buf.iter_mut() { *i = 42; } @@ -44,9 +44,27 @@ fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { Ok(()) } +// This function is used to register the custom implementation of `getrandom` +// function. +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + #[allow(unsafe_code)] + let buf = unsafe { + // fill the buffer with zeros + core::ptr::write_bytes(dest, 0, len); + // create mutable byte slice + core::slice::from_raw_parts_mut(dest, len) + }; + custom_random(buf) +} + // Many targets have very specific requirements for networking, so it's hard to // provide a generic implementation. -// Depending on the target, you will probably need to implement `Transport` trait. +// Depending on the target, you will probably need to implement `Transport` +// trait. struct MyTransport; impl Transport for MyTransport { @@ -62,8 +80,8 @@ impl Transport for MyTransport { // As our target does not have `std` library, we need to provide custom // implementation of `GlobalAlloc` trait. // -// In your program, you should use proper allocator that is supported by your target. -// Here you have dummy implementation that does nothing. +// In your program, you should use proper allocator that is supported by your +// target. Here you have dummy implementation that does nothing. #[derive(Default)] pub struct Allocator; @@ -80,23 +98,23 @@ static GLOBAL_ALLOCATOR: Allocator = Allocator; // As our target does not have `std` library, we need to provide custom // implementation of `panic_handler`. // -// In your program, you should use proper panic handler that is supported by your target. -// Here you have dummy implementation that does nothing. +// In your program, you should use proper panic handler that is supported by +// your target. Here you have dummy implementation that does nothing. #[panic_handler] fn panicking(_: &PanicInfo) -> ! { loop {} } -// As we're using `no_main` attribute, we need to define `main` function manually. -// For this example we're using `extern "C"` ABI to make it work. +// As we're using `no_main` attribute, we need to define `main` function +// manually. For this example we're using `extern "C"` ABI to make it work. #[no_mangle] pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> usize { publish_example().map(|_| 0).unwrap() } -// In standard subscribe examples we use `println` macro to print the result of the operation -// and it shows the idea of the example. `no_std` does not support `println` macro, -// so we're using `do_a_thing` function instead. +// In standard subscribe examples we use `println` macro to print the result of +// the operation and it shows the idea of the example. `no_std` does not support +// `println` macro, so we're using `do_a_thing` function instead. fn do_a_thing(_: &T) {} // As `no_std` does not support `Error` trait, we use `PubNubError` instead. @@ -115,8 +133,8 @@ fn publish_example() -> Result<(), PubNubError> { .with_user_id("user_id") .build()?; - // As `no_std` does not support `println` macro, we're using `do_a_thing` function instead - // to show possible usage of the result. + // As `no_std` does not support `println` macro, we're using `do_a_thing` + // function instead to show possible usage of the result. client .set_presence_state(State { diff --git a/examples/no_std/src/publish.rs b/examples/no_std/src/publish.rs index fae7a88b..a6ea76dc 100644 --- a/examples/no_std/src/publish.rs +++ b/examples/no_std/src/publish.rs @@ -33,11 +33,11 @@ struct Message { // As getrandom crate has limited support of targets, we need to provide custom // implementation of `getrandom` function. -getrandom::register_custom_getrandom!(custom_random); fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { // We're using `42` as a random number, because it's the answer // to the Ultimate Question of Life, the Universe, and Everything. - // In your program, you should use proper random number generator that is supported by your target. + // In your program, you should use proper random number generator that is + // supported by your target. for i in buf.iter_mut() { *i = 42; } @@ -45,9 +45,27 @@ fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { Ok(()) } +// This function is used to register the custom implementation of `getrandom` +// function. +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + #[allow(unsafe_code)] + let buf = unsafe { + // fill the buffer with zeros + core::ptr::write_bytes(dest, 0, len); + // create mutable byte slice + core::slice::from_raw_parts_mut(dest, len) + }; + custom_random(buf) +} + // Many targets have very specific requirements for networking, so it's hard to // provide a generic implementation. -// Depending on the target, you will probably need to implement `Transport` trait. +// Depending on the target, you will probably need to implement `Transport` +// trait. struct MyTransport; impl Transport for MyTransport { @@ -63,8 +81,8 @@ impl Transport for MyTransport { // As our target does not have `std` library, we need to provide custom // implementation of `GlobalAlloc` trait. // -// In your program, you should use proper allocator that is supported by your target. -// Here you have dummy implementation that does nothing. +// In your program, you should use proper allocator that is supported by your +// target. Here you have dummy implementation that does nothing. #[derive(Default)] pub struct Allocator; @@ -81,15 +99,15 @@ static GLOBAL_ALLOCATOR: Allocator = Allocator; // As our target does not have `std` library, we need to provide custom // implementation of `panic_handler`. // -// In your program, you should use proper panic handler that is supported by your target. -// Here you have dummy implementation that does nothing. +// In your program, you should use proper panic handler that is supported by +// your target. Here you have dummy implementation that does nothing. #[panic_handler] fn panicking(_: &PanicInfo) -> ! { loop {} } -// As we're using `no_main` attribute, we need to define `main` function manually. -// For this example we're using `extern "C"` ABI to make it work. +// As we're using `no_main` attribute, we need to define `main` function +// manually. For this example we're using `extern "C"` ABI to make it work. #[no_mangle] pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> usize { publish_example().map(|_| 0).unwrap() diff --git a/examples/no_std/src/subscribe.rs b/examples/no_std/src/subscribe.rs index 64606394..468a210b 100644 --- a/examples/no_std/src/subscribe.rs +++ b/examples/no_std/src/subscribe.rs @@ -34,7 +34,6 @@ struct Message { // As getrandom crate has limited support of targets, we need to provide custom // implementation of `getrandom` function. -getrandom::register_custom_getrandom!(custom_random); fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { // We're using `42` as a random number, because it's the answer // to the Ultimate Question of Life, the Universe, and Everything. @@ -47,6 +46,23 @@ fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { Ok(()) } +// This function is used to register the custom implementation of `getrandom` +// function. +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + #[allow(unsafe_code)] + let buf = unsafe { + // fill the buffer with zeros + core::ptr::write_bytes(dest, 0, len); + // create mutable byte slice + core::slice::from_raw_parts_mut(dest, len) + }; + custom_random(buf) +} + // Many targets have very specific requirements for networking, so it's hard to // provide a generic implementation. // Depending on the target, you will probably need to implement `Transport` diff --git a/examples/no_std/src/where_now.rs b/examples/no_std/src/where_now.rs index b2c0490c..74d0e995 100644 --- a/examples/no_std/src/where_now.rs +++ b/examples/no_std/src/where_now.rs @@ -25,7 +25,6 @@ use pubnub::{ // As getrandom crate has limited support of targets, we need to provide custom // implementation of `getrandom` function. -getrandom::register_custom_getrandom!(custom_random); fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { // We're using `42` as a random number, because it's the answer // to the Ultimate Question of Life, the Universe, and Everything. @@ -37,6 +36,21 @@ fn custom_random(buf: &mut [u8]) -> Result<(), getrandom::Error> { Ok(()) } +// This function is used to register the custom implementation of `getrandom` function. +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + let buf = unsafe { + // fill the buffer with zeros + core::ptr::write_bytes(dest, 0, len); + // create mutable byte slice + core::slice::from_raw_parts_mut(dest, len) + }; + custom_random(buf) +} + // Many targets have very specific requirements for networking, so it's hard to // provide a generic implementation. // Depending on the target, you will probably need to implement `Transport` trait. diff --git a/examples/presence_state.rs b/examples/presence_state.rs index 25b97d4f..4494dbf4 100644 --- a/examples/presence_state.rs +++ b/examples/presence_state.rs @@ -7,11 +7,6 @@ struct State { is_doing: String, flag: bool, } -#[derive(Debug, serde::Serialize)] -struct State2 { - is_doing: String, - business: String, -} #[tokio::main] async fn main() -> Result<(), Box> { diff --git a/src/core/channel.rs b/src/core/channel.rs index 6795e414..33164139 100644 --- a/src/core/channel.rs +++ b/src/core/channel.rs @@ -111,8 +111,7 @@ impl Channel { /// > features are enabled. /// /// > As long as entity used by at least one subscription it can't be - /// > removed from subscription - /// loop. + /// > removed from subscription loop. #[cfg(all(feature = "subscribe", feature = "std"))] pub(crate) fn decrease_subscriptions_count(&self) { let mut subscriptions_count_slot = self.subscriptions_count.write(); diff --git a/src/core/channel_group.rs b/src/core/channel_group.rs index 2a843788..87e10b20 100644 --- a/src/core/channel_group.rs +++ b/src/core/channel_group.rs @@ -112,8 +112,7 @@ impl ChannelGroup { /// > features are enabled. /// /// > As long as entity used by at least one subscription it can't be - /// > removed from subscription - /// loop. + /// > removed from subscription loop. #[cfg(all(feature = "subscribe", feature = "std"))] pub(crate) fn decrease_subscriptions_count(&self) { let mut subscriptions_count_slot = self.subscriptions_count.write(); diff --git a/src/core/channel_metadata.rs b/src/core/channel_metadata.rs index 35159929..f5eb0745 100644 --- a/src/core/channel_metadata.rs +++ b/src/core/channel_metadata.rs @@ -112,8 +112,7 @@ impl ChannelMetadata { /// > features are enabled. /// /// > As long as entity used by at least one subscription it can't be - /// > removed from subscription - /// loop. + /// > removed from subscription loop. #[cfg(all(feature = "subscribe", feature = "std"))] pub(crate) fn decrease_subscriptions_count(&self) { let mut subscriptions_count_slot = self.subscriptions_count.write(); diff --git a/src/core/entity.rs b/src/core/entity.rs index 96e78ac4..7e2d9fc6 100644 --- a/src/core/entity.rs +++ b/src/core/entity.rs @@ -74,8 +74,7 @@ impl PubNubEntity { /// > features are enabled. /// /// > As long as entity used by at least one subscription it can't be - /// > removed from subscription - /// loop. + /// > removed from subscription loop. #[cfg(all(feature = "subscribe", feature = "std"))] pub(crate) fn decrease_subscriptions_count(&self) { match self { diff --git a/src/core/event_engine/effect_dispatcher.rs b/src/core/event_engine/effect_dispatcher.rs index ea219e49..6fa8559a 100644 --- a/src/core/event_engine/effect_dispatcher.rs +++ b/src/core/event_engine/effect_dispatcher.rs @@ -159,7 +159,6 @@ where mod should { use super::*; use crate::core::event_engine::Event; - use std::future::Future; struct TestEvent; @@ -261,27 +260,6 @@ mod should { } } - #[derive(Clone)] - struct TestRuntime {} - - #[async_trait::async_trait] - impl Runtime for TestRuntime { - fn spawn(&self, _future: impl Future + Send + 'static) - where - R: Send + 'static, - { - // Do nothing. - } - - async fn sleep(self, _delay: u64) { - // Do nothing. - } - - async fn sleep_microseconds(self, _delay: u64) { - // Do nothing. - } - } - #[test] fn create_not_managed_effect() { let (_tx, rx) = async_channel::bounded::(5); diff --git a/src/core/event_engine/mod.rs b/src/core/event_engine/mod.rs index dae326ec..c5d018d4 100644 --- a/src/core/event_engine/mod.rs +++ b/src/core/event_engine/mod.rs @@ -174,7 +174,7 @@ where /// Stop state machine using specific invocation. /// /// > Note: Should be provided effect information which respond with `true` - /// for `is_terminating` method call. + /// > for `is_terminating` method call. pub fn stop(&self, invocation: EI) { { *self.active.write() = false; diff --git a/src/core/event_engine/transition.rs b/src/core/event_engine/transition.rs index b334f934..6d676aa2 100644 --- a/src/core/event_engine/transition.rs +++ b/src/core/event_engine/transition.rs @@ -7,7 +7,6 @@ use crate::{ /// /// State transition with information about target state and list of effect /// invocations. - pub(crate) struct Transition where S: State, diff --git a/src/core/retry_policy.rs b/src/core/retry_policy.rs index 37dd0146..caaf92bd 100644 --- a/src/core/retry_policy.rs +++ b/src/core/retry_policy.rs @@ -8,8 +8,6 @@ //! [`PubNub API`]: https://www.pubnub.com/docs //! [`pubnub`]: ../index.html -use getrandom::getrandom; - use crate::{core::PubNubError, lib::alloc::vec::Vec}; /// List of known endpoint groups (by context) @@ -287,7 +285,7 @@ impl RequestRetryConfiguration { fn reached_max_retry(&self, attempt: &u8) -> bool { match self { Self::Linear { max_retry, .. } | Self::Exponential { max_retry, .. } => { - attempt.gt(max_retry) + attempt.ge(max_retry) } _ => false, } @@ -304,15 +302,13 @@ impl RequestRetryConfiguration { /// * `Some(delay_in_microseconds)` - The delay in microseconds. /// * `None` - If `delay_in_seconds` is `None`. fn delay_in_microseconds(delay_in_seconds: Option) -> Option { - let Some(delay_in_seconds) = delay_in_seconds else { - return None; - }; + let delay_in_seconds = delay_in_seconds?; const MICROS_IN_SECOND: u64 = 1_000_000; let delay = delay_in_seconds * MICROS_IN_SECOND; let mut random_bytes = [0u8; 8]; - if getrandom(&mut random_bytes).is_err() { + if getrandom::fill(&mut random_bytes).is_err() { return Some(delay); } @@ -557,7 +553,7 @@ mod should { fn return_service_delay_for_too_many_requests_error_response() { let policy = RequestRetryConfiguration::Linear { delay: 10, - max_retry: 2, + max_retry: 3, excluded_endpoints: None, }; @@ -721,7 +717,7 @@ mod should { let policy = RequestRetryConfiguration::Exponential { min_delay: 10, max_delay: 100, - max_retry: 2, + max_retry: 3, excluded_endpoints: None, }; diff --git a/src/core/transport_request.rs b/src/core/transport_request.rs index bcd918f4..483dca60 100644 --- a/src/core/transport_request.rs +++ b/src/core/transport_request.rs @@ -126,13 +126,6 @@ impl TransportRequest { break; }; - // Subscribe and heartbeat handled by Event Engine. - if self.path.starts_with("/v2/subscribe") - || (self.path.starts_with("/v2/presence") && self.path.contains("/heartbeat")) - { - break; - } - if let Some(delay) = retry_configuration.retry_delay( Some(self.path.clone()), &retry_attempt, @@ -195,13 +188,6 @@ impl TransportRequest { break; }; - // Subscribe and heartbeat handled by Event Engine. - if self.path.starts_with("/v2/subscribe") - || (self.path.starts_with("/v2/presence") && self.path.contains("/heartbeat")) - { - break; - } - if let Some(delay) = retry_configuration.retry_delay( Some(self.path.clone()), &retry_attempt, diff --git a/src/core/user_metadata.rs b/src/core/user_metadata.rs index 613da256..c204094f 100644 --- a/src/core/user_metadata.rs +++ b/src/core/user_metadata.rs @@ -112,8 +112,7 @@ impl UserMetadata { /// > features are enabled. /// /// > As long as entity used by at least one subscription it can't be - /// > removed from subscription - /// loop. + /// > removed from subscription loop. #[cfg(all(feature = "subscribe", feature = "std"))] pub(crate) fn decrease_subscriptions_count(&self) { let mut subscriptions_count_slot = self.subscriptions_count.write(); diff --git a/src/dx/access/mod.rs b/src/dx/access/mod.rs index 03bf7f35..77e41cf6 100644 --- a/src/dx/access/mod.rs +++ b/src/dx/access/mod.rs @@ -77,7 +77,7 @@ impl PubNubClientInstance { /// # } /// ``` #[cfg(feature = "serde")] - pub fn grant_token(&self, ttl: usize) -> GrantTokenRequestBuilder { + pub fn grant_token(&self, ttl: usize) -> GrantTokenRequestBuilder<'_, T, SerdeSerializer, D> { GrantTokenRequestBuilder { pubnub_client: Some(self.clone()), serializer: Some(SerdeSerializer), @@ -350,7 +350,6 @@ mod it_should { response: None, request_handler: Some(Box::new(|req| { assert!(req.query_parameters.contains_key("timestamp")); - assert!(req.query_parameters.get("timestamp").is_some()); })), }; @@ -368,7 +367,6 @@ mod it_should { response: None, request_handler: Some(Box::new(|req| { assert!(req.query_parameters.contains_key("signature")); - assert!(req.query_parameters.get("signature").is_some()); assert!(req .query_parameters .get("signature") diff --git a/src/dx/presence/builders/heartbeat.rs b/src/dx/presence/builders/heartbeat.rs index 7169287e..68eb10fc 100644 --- a/src/dx/presence/builders/heartbeat.rs +++ b/src/dx/presence/builders/heartbeat.rs @@ -4,8 +4,6 @@ //! announce specified `user_id` presence in the provided channels and groups. use derive_builder::Builder; -#[cfg(feature = "std")] -use futures::{future::BoxFuture, select_biased, FutureExt}; use crate::{ core::{ @@ -31,9 +29,6 @@ use crate::{ }, }; -#[cfg(feature = "std")] -use crate::{core::event_engine::cancel::CancellationTask, lib::alloc::sync::Arc}; - /// The [`HeartbeatRequestsBuilder`] is used to build a `user_id` presence /// announcement request that is sent to the [`PubNub`] network. /// @@ -61,7 +56,7 @@ pub struct HeartbeatRequest { /// Channel(s) for announcement. #[builder( field(vis = "pub(in crate::dx::presence)"), - setter(strip_option, into), + setter(custom, strip_option), default = "vec![]" )] pub(in crate::dx::presence) channels: Vec, @@ -69,7 +64,7 @@ pub struct HeartbeatRequest { /// Channel group(s) for announcement. #[builder( field(vis = "pub(in crate::dx::presence)"), - setter(into, strip_option), + setter(custom, strip_option), default = "vec![]" )] pub(in crate::dx::presence) channel_groups: Vec, @@ -125,6 +120,69 @@ pub struct HeartbeatRequest { } impl HeartbeatRequestBuilder { + /// Channel(s) for announcement. + pub fn channels(mut self, channels: L) -> Self + where + L: Into>, + { + let mut unique = channels.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channels = Some(unique); + self + } + + /// Channel group(s) for announcement. + pub fn channel_groups(mut self, channel_groups: L) -> Self + where + L: Into>, + { + let mut unique = channel_groups.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channel_groups = Some(unique); + self + } + + /// A state that should be associated with the `user_id`. + /// + /// `state` object should be a `HashMap` with channel names as keys and + /// nested `HashMap` with values. State with heartbeat can be set **only** + /// for channels. + /// + /// # Example: + /// ```rust,no_run + /// # use std::collections::HashMap; + /// # use pubnub::core::Serialize; + /// # fn main() -> Result<(), pubnub::core::PubNubError> { + /// let state: HashMap> = HashMap::from([( + /// "announce".to_string(), + /// HashMap::::from([ + /// ("is_owner".to_string(), false), + /// ("is_admin".to_string(), true) + /// ]) + /// )]); + /// # Ok(()) + /// # } + /// ``` + pub fn state(mut self, state: HashMap>) -> Self { + let mut serialized_state = vec![b'{']; + for (key, mut value) in state { + serialized_state.append(&mut format!("\"{}\":", key).as_bytes().to_vec()); + serialized_state.append(&mut value); + serialized_state.push(b','); + } + if serialized_state.last() == Some(&b',') { + serialized_state.pop(); + } + serialized_state.push(b'}'); + + self.state = Some(Some(serialized_state)); + self + } + /// Validate user-provided data for request builder. /// /// Validator ensure that provided information is enough to build valid @@ -189,45 +247,6 @@ impl HeartbeatRequest { } } -impl HeartbeatRequestBuilder { - /// A state that should be associated with the `user_id`. - /// - /// `state` object should be a `HashMap` with channel names as keys and - /// nested `HashMap` with values. State with heartbeat can be set **only** - /// for channels. - /// - /// # Example: - /// ```rust,no_run - /// # use std::collections::HashMap; - /// # use pubnub::core::Serialize; - /// # fn main() -> Result<(), pubnub::core::PubNubError> { - /// let state: HashMap> = HashMap::from([( - /// "announce".to_string(), - /// HashMap::::from([ - /// ("is_owner".to_string(), false), - /// ("is_admin".to_string(), true) - /// ]) - /// )]); - /// # Ok(()) - /// # } - /// ``` - pub fn state(mut self, state: HashMap>) -> Self { - let mut serialized_state = vec![b'{']; - for (key, mut value) in state { - serialized_state.append(&mut format!("\"{}\":", key).as_bytes().to_vec()); - serialized_state.append(&mut value); - serialized_state.push(b','); - } - if serialized_state.last() == Some(&b',') { - serialized_state.pop(); - } - serialized_state.push(b'}'); - - self.state = Some(Some(serialized_state)); - self - } -} - impl HeartbeatRequestBuilder where T: Transport + 'static, @@ -251,40 +270,6 @@ where ) .await } - - /// Build and call asynchronous request after delay. - /// - /// Perform delayed request call with ability to cancel it before call. - #[cfg(feature = "std")] - pub(in crate::dx::presence) async fn execute_with_cancel_and_delay( - self, - delay: Arc, - cancel_task: CancellationTask, - ) -> Result - where - F: Fn() -> BoxFuture<'static, ()> + Send + Sync + 'static, - { - select_biased! { - _ = cancel_task.wait_for_cancel().fuse() => { - Err(PubNubError::EffectCanceled) - }, - response = self.execute_with_delay(delay).fuse() => { - response - } - } - } - - /// Build and call asynchronous request after configured delay. - #[cfg(feature = "std")] - async fn execute_with_delay(self, delay: Arc) -> Result - where - F: Fn() -> BoxFuture<'static, ()> + Send + Sync + 'static, - { - // Postpone request execution. - delay().await; - - self.execute().await - } } #[cfg(feature = "blocking")] @@ -303,47 +288,3 @@ where .send_blocking::(&client.transport, deserializer) } } - -#[cfg(feature = "std")] -#[cfg(test)] -mod it_should { - use super::*; - use crate::{core::TransportResponse, PubNubClientBuilder}; - use futures::future::ready; - - #[tokio::test] - async fn be_able_to_cancel_delayed_heartbeat_call() { - struct MockTransport; - - #[async_trait::async_trait] - impl Transport for MockTransport { - async fn send(&self, _req: TransportRequest) -> Result { - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; // Simulate long request. - - Ok(TransportResponse::default()) - } - } - - let (tx, rx) = async_channel::bounded(1); - - let cancel_task = CancellationTask::new(rx, "test".into()); - - tx.send("test".into()).await.unwrap(); - - let result = PubNubClientBuilder::with_transport(MockTransport) - .with_keyset(crate::Keyset { - subscribe_key: "test", - publish_key: Some("test"), - secret_key: None, - }) - .with_user_id("test") - .build() - .unwrap() - .heartbeat() - .channels(vec!["test".into()]) - .execute_with_cancel_and_delay(Arc::new(|| ready(()).boxed()), cancel_task) - .await; - - assert!(matches!(result, Err(PubNubError::EffectCanceled))); - } -} diff --git a/src/dx/presence/builders/here_now.rs b/src/dx/presence/builders/here_now.rs index 6d58381c..99046e68 100644 --- a/src/dx/presence/builders/here_now.rs +++ b/src/dx/presence/builders/here_now.rs @@ -81,6 +81,24 @@ pub struct HereNowRequest { default = "false" )] pub(in crate::dx::presence) include_state: bool, + + /// Maximum number of occupants to return per channel. + /// + /// > **Important**: Maximum and default value is `1000` users per request. + #[builder( + field(vis = "pub(in crate::dx::presence)"), + setter(strip_option), + default = "1000" + )] + pub(in crate::dx::presence) limit: usize, + + /// Zero-based starting index for pagination. + #[builder( + field(vis = "pub(in crate::dx::presence)"), + setter(strip_option), + default + )] + pub(in crate::dx::presence) offset: Option, } impl HereNowRequestBuilder { @@ -127,6 +145,12 @@ impl HereNowRequest { query.insert("disable_uuids".into(), "1".into()); }); + query.insert("limit".into(), self.limit.to_string()); + + if let Some(offset) = self.offset { + (offset > 0).then(|| query.insert("offset".into(), offset.to_string())); + } + Ok(TransportRequest { path: format!( "/v2/presence/sub-key/{}/channel/{}", diff --git a/src/dx/presence/builders/leave.rs b/src/dx/presence/builders/leave.rs index 519d4b42..8162745f 100644 --- a/src/dx/presence/builders/leave.rs +++ b/src/dx/presence/builders/leave.rs @@ -53,18 +53,18 @@ pub struct LeaveRequest { #[builder(field(vis = "pub(in crate::dx::presence)"), setter(custom))] pub(in crate::dx::presence) pubnub_client: PubNubClientInstance, - /// Channels for announcement. + /// Channel(s) for announcement. #[builder( field(vis = "pub(in crate::dx::presence)"), - setter(strip_option, into), + setter(custom, strip_option), default = "vec![]" )] pub(in crate::dx::presence) channels: Vec, - /// Channel groups for announcement. + /// Channel group(s) for announcement. #[builder( field(vis = "pub(in crate::dx::presence)"), - setter(into, strip_option), + setter(custom, strip_option), default = "vec![]" )] pub(in crate::dx::presence) channel_groups: Vec, @@ -76,6 +76,32 @@ pub struct LeaveRequest { } impl LeaveRequestBuilder { + /// Channel(s) for announcement. + pub fn channels(mut self, channels: L) -> Self + where + L: Into>, + { + let mut unique = channels.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channels = Some(unique); + self + } + + /// Channel group(s) for announcement. + pub fn channel_groups(mut self, channel_groups: L) -> Self + where + L: Into>, + { + let mut unique = channel_groups.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channel_groups = Some(unique); + self + } + /// Validate user-provided data for request builder. /// /// Validator ensure that list of provided data is enough to build valid diff --git a/src/dx/presence/event_engine/effect_handler.rs b/src/dx/presence/event_engine/effect_handler.rs index 482a74a3..d4cf2dd6 100644 --- a/src/dx/presence/event_engine/effect_handler.rs +++ b/src/dx/presence/event_engine/effect_handler.rs @@ -8,7 +8,7 @@ use spin::RwLock; use uuid::Uuid; use crate::{ - core::{event_engine::EffectHandler, RequestRetryConfiguration}, + core::event_engine::EffectHandler, lib::{ alloc::sync::Arc, core::fmt::{Debug, Formatter, Result}, @@ -27,18 +27,12 @@ pub(crate) struct PresenceEffectHandler { /// Heartbeat call function pointer. heartbeat_call: Arc, - /// Delayed heartbeat call function pointer. - delayed_heartbeat_call: Arc, - /// Leave function pointer. leave_call: Arc, /// Heartbeat interval wait function pointer. wait_call: Arc, - /// Retry policy. - retry_policy: RequestRetryConfiguration, - /// Cancellation channel. cancellation_channel: Sender, } @@ -47,18 +41,14 @@ impl PresenceEffectHandler { /// Create presence effect handler. pub fn new( heartbeat_call: Arc, - delayed_heartbeat_call: Arc, leave_call: Arc, wait_call: Arc, - retry_policy: RequestRetryConfiguration, cancellation_channel: Sender, ) -> Self { Self { heartbeat_call, - delayed_heartbeat_call, leave_call, wait_call, - retry_policy, cancellation_channel, } } @@ -72,20 +62,6 @@ impl EffectHandler for PresenceEffectH input: input.clone(), executor: self.heartbeat_call.clone(), }), - PresenceEffectInvocation::DelayedHeartbeat { - input, - attempts, - reason, - } => Some(PresenceEffect::DelayedHeartbeat { - id: Uuid::new_v4().to_string(), - cancelled: RwLock::new(false), - input: input.clone(), - attempts: *attempts, - reason: reason.clone(), - retry_policy: self.retry_policy.clone(), - executor: self.delayed_heartbeat_call.clone(), - cancellation_channel: self.cancellation_channel.clone(), - }), PresenceEffectInvocation::Leave { input } => Some(PresenceEffect::Leave { id: Uuid::new_v4().to_string(), input: input.clone(), diff --git a/src/dx/presence/event_engine/effects/heartbeat.rs b/src/dx/presence/event_engine/effects/heartbeat.rs index ecd14cb2..669e916c 100644 --- a/src/dx/presence/event_engine/effects/heartbeat.rs +++ b/src/dx/presence/event_engine/effects/heartbeat.rs @@ -8,7 +8,7 @@ use futures::TryFutureExt; use log::info; use crate::{ - core::{PubNubError, RequestRetryConfiguration}, + core::PubNubError, lib::alloc::{sync::Arc, vec, vec::Vec}, presence::event_engine::{ effects::HeartbeatEffectExecutor, PresenceEvent, PresenceInput, PresenceParameters, @@ -18,18 +18,8 @@ use crate::{ #[allow(clippy::too_many_arguments)] pub(super) async fn execute( input: &PresenceInput, - attempt: u8, - reason: Option, - effect_id: &str, - retry_policy: &RequestRetryConfiguration, executor: &Arc, ) -> Vec { - if let Some(reason) = reason.clone() { - if !retry_policy.retriable(Some("/v2/presence"), &attempt, Some(&reason)) { - return vec![PresenceEvent::HeartbeatGiveUp { reason }]; - } - } - let channel_groups: Option> = input.channel_groups(); let channels: Option> = input.channels(); @@ -41,9 +31,6 @@ pub(super) async fn execute( executor(PresenceParameters { channels: &channels, channel_groups: &channel_groups, - attempt, - reason, - effect_id, }) .map_ok_or_else( |error| { @@ -73,9 +60,6 @@ mod it_should { let mocked_heartbeat_function: Arc = Arc::new(move |parameters| { assert_eq!(parameters.channel_groups, &Some(vec!["cg1".to_string()])); assert_eq!(parameters.channels, &Some(vec!["ch1".to_string()])); - assert_eq!(parameters.attempt, 0); - assert_eq!(parameters.reason, None); - assert_eq!(parameters.effect_id, "id"); async move { Ok(HeartbeatResult) }.boxed() }); @@ -85,10 +69,6 @@ mod it_should { &Some(vec!["ch1".to_string()]), &Some(vec!["cg1".to_string()]), ), - 0, - None, - "id", - &RequestRetryConfiguration::None, &mocked_heartbeat_function, ) .await; @@ -120,20 +100,6 @@ mod it_should { &Some(vec!["ch1".to_string()]), &Some(vec!["cg1".to_string()]), ), - 0, - Some(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }), - "id", - &RequestRetryConfiguration::Linear { - max_retry: 5, - delay: 2, - excluded_endpoints: None, - }, &mocked_heartbeat_function, ) .await; @@ -145,51 +111,6 @@ mod it_should { )); } - #[tokio::test] - async fn return_heartbeat_give_up_event_on_error() { - let mocked_heartbeat_function: Arc = Arc::new(move |_| { - async move { - Err(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - } - .boxed() - }); - - let result = execute( - &PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - 5, - Some(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }), - "id", - &RequestRetryConfiguration::Linear { - delay: 0, - max_retry: 1, - excluded_endpoints: None, - }, - &mocked_heartbeat_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - PresenceEvent::HeartbeatGiveUp { .. } - )); - } - #[tokio::test] async fn return_empty_event_on_effect_cancel_err() { let mocked_heartbeat_function: Arc = @@ -200,69 +121,10 @@ mod it_should { &Some(vec!["ch1".to_string()]), &Some(vec!["cg1".to_string()]), ), - 5, - Some(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }), - "id", - &RequestRetryConfiguration::Linear { - max_retry: 5, - delay: 2, - excluded_endpoints: None, - }, &mocked_heartbeat_function, ) .await; assert!(result.is_empty()); } - - #[tokio::test] - async fn return_heartbeat_give_up_event_on_error_with_none_auto_retry_policy() { - let mocked_heartbeat_function: Arc = Arc::new(move |_| { - async move { - Err(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - } - .boxed() - }); - - let result = execute( - &PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - 5, - Some(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }), - "id", - &RequestRetryConfiguration::Linear { - delay: 0, - max_retry: 1, - excluded_endpoints: None, - }, - &mocked_heartbeat_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - PresenceEvent::HeartbeatGiveUp { .. } - )); - } } diff --git a/src/dx/presence/event_engine/effects/leave.rs b/src/dx/presence/event_engine/effects/leave.rs index d5c9cdf1..e827e550 100644 --- a/src/dx/presence/event_engine/effects/leave.rs +++ b/src/dx/presence/event_engine/effects/leave.rs @@ -15,7 +15,6 @@ use crate::{ #[allow(clippy::too_many_arguments, dead_code)] pub(super) async fn execute( input: &PresenceInput, - effect_id: &str, executor: &Arc, ) -> Vec { let channel_groups = input.channel_groups(); @@ -29,9 +28,6 @@ pub(super) async fn execute( let _ = executor(PresenceParameters { channels: &channels, channel_groups: &channel_groups, - attempt: 0, - reason: None, - effect_id, }) .await; @@ -53,9 +49,6 @@ mod it_should { let mocked_leave_function: Arc = Arc::new(move |parameters| { assert_eq!(parameters.channel_groups, &Some(vec!["cg2".to_string()])); assert_eq!(parameters.channels, &Some(vec!["ch2".to_string()])); - assert_eq!(parameters.attempt, 0); - assert_eq!(parameters.reason, None); - assert_eq!(parameters.effect_id, "id"); async move { Ok(LeaveResult) }.boxed() }); @@ -65,7 +58,6 @@ mod it_should { &Some(vec!["ch2".to_string()]), &Some(vec!["cg2".to_string()]), ), - "id", &mocked_leave_function, ) .await; @@ -93,7 +85,6 @@ mod it_should { &Some(vec!["ch3".to_string()]), &Some(vec!["cg3".to_string()]), ), - "id", &mocked_leave_function, ) .await; diff --git a/src/dx/presence/event_engine/effects/mod.rs b/src/dx/presence/event_engine/effects/mod.rs index 07528ada..3cb07f16 100644 --- a/src/dx/presence/event_engine/effects/mod.rs +++ b/src/dx/presence/event_engine/effects/mod.rs @@ -7,7 +7,7 @@ use spin::RwLock; use crate::{ core::{ event_engine::{Effect, EffectInvocation}, - PubNubError, RequestRetryConfiguration, + PubNubError, }, lib::{ alloc::{string::String, sync::Arc, vec::Vec}, @@ -69,42 +69,6 @@ pub(crate) enum PresenceEffect { executor: Arc, }, - /// Delayed heartbeat effect invocation. - DelayedHeartbeat { - /// Unique effect identifier. - id: String, - - /// Whether delayed heartbeat effect has been cancelled or not. - cancelled: RwLock, - - /// User input with channels and groups. - /// - /// Object contains list of channels and groups for which `user_id` - /// presence should be announced. - input: PresenceInput, - - /// Current heartbeat retry attempt. - /// - /// Used to track overall number of heartbeat retry attempts. - attempts: u8, - - /// Heartbeat attempt failure reason. - reason: PubNubError, - - /// Retry policy. - retry_policy: RequestRetryConfiguration, - - /// Executor function. - /// - /// Function which will be used to execute heartbeat. - executor: Arc, - - /// Cancellation channel. - /// - /// Channel which will be used to cancel effect execution. - cancellation_channel: Sender, - }, - /// Leave effect invocation. Leave { /// Unique effect identifier. @@ -157,12 +121,6 @@ impl Debug for PresenceEffect { input.channels(), input.channel_groups() ), - Self::DelayedHeartbeat { input, .. } => write!( - f, - "PresenceEffect::DelayedHeartbeat {{ channels: {:?}, channel groups: {:?}}}", - input.channels(), - input.channel_groups() - ), Self::Leave { input, .. } => write!( f, "PresenceEffect::Leave {{ channels: {:?}, channel groups: {:?}}}", @@ -186,7 +144,6 @@ impl Effect for PresenceEffect { fn name(&self) -> String { match self { Self::Heartbeat { .. } => "HEARTBEAT", - Self::DelayedHeartbeat { .. } => "DELAYED_HEARTBEAT", Self::Leave { .. } => "LEAVE", Self::Wait { .. } => "WAIT", } @@ -195,10 +152,7 @@ impl Effect for PresenceEffect { fn id(&self) -> String { match self { - Self::Heartbeat { id, .. } - | Self::DelayedHeartbeat { id, .. } - | Self::Leave { id, .. } - | Self::Wait { id, .. } => id, + Self::Heartbeat { id, .. } | Self::Leave { id, .. } | Self::Wait { id, .. } => id, } .into() } @@ -206,57 +160,18 @@ impl Effect for PresenceEffect { async fn run(&self) -> Vec<::Event> { match self { Self::Heartbeat { - id, - input, - executor, - } => { - heartbeat::execute( - input, - 0, - None, - id, - &RequestRetryConfiguration::None, - executor, - ) - .await - } - Self::DelayedHeartbeat { - id, - input, - attempts, - reason, - retry_policy, - executor, - .. - } => { - heartbeat::execute( - input, - *attempts, - Some(reason.clone()), - id, - &retry_policy.clone(), - executor, - ) - .await - } + input, executor, .. + } => heartbeat::execute(input, executor).await, Self::Leave { - id, - input, - executor, - } => leave::execute(input, id, executor).await, + input, executor, .. + } => leave::execute(input, executor).await, Self::Wait { id, executor, .. } => wait::execute(id, executor).await, } } fn cancel(&self) { match self { - PresenceEffect::DelayedHeartbeat { - id, - cancelled, - cancellation_channel, - .. - } - | PresenceEffect::Wait { + PresenceEffect::Wait { id, cancelled, cancellation_channel, @@ -276,9 +191,7 @@ impl Effect for PresenceEffect { fn is_cancelled(&self) -> bool { match self { - Self::DelayedHeartbeat { cancelled, .. } | Self::Wait { cancelled, .. } => { - *cancelled.read() - } + Self::Wait { cancelled, .. } => *cancelled.read(), _ => false, } } @@ -304,23 +217,4 @@ mod it_should { effect.cancel(); assert_eq!(rx.recv().await.unwrap(), effect.id()) } - - #[tokio::test] - async fn send_delayed_heartbeat_cancellation_notification() { - let (tx, rx) = async_channel::bounded(1); - - let effect = PresenceEffect::DelayedHeartbeat { - id: Uuid::new_v4().to_string(), - cancelled: RwLock::new(false), - input: PresenceInput::new(&None, &None), - attempts: 0, - reason: PubNubError::EffectCanceled, - retry_policy: Default::default(), - executor: Arc::new(|_| Box::pin(async move { Err(PubNubError::EffectCanceled) })), - cancellation_channel: tx, - }; - - effect.cancel(); - assert_eq!(rx.recv().await.unwrap(), effect.id()) - } } diff --git a/src/dx/presence/event_engine/event.rs b/src/dx/presence/event_engine/event.rs index 7addcec9..97c4f1fd 100644 --- a/src/dx/presence/event_engine/event.rs +++ b/src/dx/presence/event_engine/event.rs @@ -73,13 +73,6 @@ pub(crate) enum PresenceEvent { /// [`PubNub`]: https://www.pubnub.com/ HeartbeatFailure { reason: PubNubError }, - /// All heartbeat attempts was unsuccessful. - /// - /// Emitted when heartbeat attempts reached maximum allowed count (according - /// to retry / reconnection policy) and all following attempts should be - /// stopped. - HeartbeatGiveUp { reason: PubNubError }, - /// Restore heartbeating. /// /// Re-launch heartbeat event engine. @@ -106,7 +99,6 @@ impl Event for PresenceEvent { Self::LeftAll { .. } => "LEFT_ALL", Self::HeartbeatSuccess => "HEARTBEAT_SUCCESS", Self::HeartbeatFailure { .. } => "HEARTBEAT_FAILURE", - Self::HeartbeatGiveUp { .. } => "HEARTBEAT_GIVEUP", Self::Reconnect => "RECONNECT", Self::Disconnect => "DISCONNECT", Self::TimesUp => "TIMES_UP", diff --git a/src/dx/presence/event_engine/invocation.rs b/src/dx/presence/event_engine/invocation.rs index 611fdec3..fb0d855f 100644 --- a/src/dx/presence/event_engine/invocation.rs +++ b/src/dx/presence/event_engine/invocation.rs @@ -4,7 +4,7 @@ //! available event engine effect invocations. use crate::{ - core::{event_engine::EffectInvocation, PubNubError}, + core::event_engine::EffectInvocation, lib::core::fmt::{Display, Formatter, Result}, presence::event_engine::{PresenceEffect, PresenceEvent, PresenceInput}, }; @@ -20,26 +20,6 @@ pub(crate) enum PresenceEffectInvocation { input: PresenceInput, }, - /// Delayed heartbeat effect invocation. - DelayedHeartbeat { - /// User input with channels and groups. - /// - /// Object contains list of channels and groups for which `user_id` - /// presence should be announced. - input: PresenceInput, - - /// Delayed heartbeat retry attempt. - /// - /// Used to track overall number of delayed heartbeat retry attempts. - attempts: u8, - - /// Delayed heartbeat attempt failure reason. - reason: PubNubError, - }, - - /// Cancel delayed heartbeat effect invocation. - CancelDelayedHeartbeat, - /// Leave effect invocation. Leave { /// User input with channels and groups. @@ -72,8 +52,6 @@ impl EffectInvocation for PresenceEffectInvocation { fn id(&self) -> &str { match self { Self::Heartbeat { .. } => "HEARTBEAT", - Self::DelayedHeartbeat { .. } => "DELAYED_HEARTBEAT", - Self::CancelDelayedHeartbeat => "CANCEL_DELAYED_HEARTBEAT", Self::Leave { .. } => "LEAVE", Self::Wait { .. } => "WAIT", Self::CancelWait => "CANCEL_WAIT", @@ -82,18 +60,15 @@ impl EffectInvocation for PresenceEffectInvocation { } fn is_managed(&self) -> bool { - matches!(self, Self::Wait { .. } | Self::DelayedHeartbeat { .. }) + matches!(self, Self::Wait { .. }) } fn is_cancelling(&self) -> bool { - matches!(self, Self::CancelDelayedHeartbeat | Self::CancelWait) + matches!(self, Self::CancelWait) } fn cancelling_effect(&self, effect: &Self::Effect) -> bool { - (matches!(effect, PresenceEffect::DelayedHeartbeat { .. }) - && matches!(self, Self::CancelDelayedHeartbeat { .. })) - || (matches!(effect, PresenceEffect::Wait { .. }) - && matches!(self, Self::CancelWait { .. })) + matches!(effect, PresenceEffect::Wait { .. }) && matches!(self, Self::CancelWait { .. }) } fn is_terminating(&self) -> bool { @@ -105,8 +80,6 @@ impl Display for PresenceEffectInvocation { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { Self::Heartbeat { .. } => write!(f, "HEARTBEAT"), - Self::DelayedHeartbeat { .. } => write!(f, "DELAYED_HEARTBEAT"), - Self::CancelDelayedHeartbeat => write!(f, "CANCEL_DELAYED_HEARTBEAT"), Self::Leave { .. } => write!(f, "LEAVE"), Self::Wait { .. } => write!(f, "WAIT"), Self::CancelWait => write!(f, "CANCEL_WAIT"), diff --git a/src/dx/presence/event_engine/state.rs b/src/dx/presence/event_engine/state.rs index 076bb4c2..d32bc7e7 100644 --- a/src/dx/presence/event_engine/state.rs +++ b/src/dx/presence/event_engine/state.rs @@ -50,25 +50,6 @@ pub(crate) enum PresenceState { input: PresenceInput, }, - /// Heartbeat recovering state. - /// - /// The system is recovering after heartbeating attempt failure. - Reconnecting { - /// User input with channels and groups. - /// - /// Object contains list of channels and groups for which `user_id` - /// presence will be announced after heartbeating restore. - input: PresenceInput, - - /// Current heartbeating retry attempt. - /// - /// Used to track overall number of heartbeating retry attempts. - attempts: u8, - - /// Heartbeating attempt failure reason. - reason: PubNubError, - }, - /// Heartbeat stopped state. /// /// Heartbeat explicitly has been stopped in response on user actions with @@ -117,7 +98,6 @@ impl PresenceState { } Self::Heartbeating { input } | Self::Cooldown { input } - | Self::Reconnecting { input, .. } | Self::Failed { input, .. } | Self::Stopped { input } if &event_input != input => @@ -148,7 +128,6 @@ impl PresenceState { match self { Self::Heartbeating { input } | Self::Cooldown { input } - | Self::Reconnecting { input, .. } | Self::Failed { input, .. } | Self::Stopped { input } => { let channels_for_heartbeating = input.clone() - event_input.clone(); @@ -192,7 +171,6 @@ impl PresenceState { match self { Self::Heartbeating { input } | Self::Cooldown { input } - | Self::Reconnecting { input, .. } | Self::Failed { input, .. } => Some(self.transition_to( Some(Self::Inactive), (!suppress_leave_events).then(|| { @@ -211,14 +189,12 @@ impl PresenceState { &self, ) -> Option> { match self { - Self::Heartbeating { input } | Self::Reconnecting { input, .. } => { - Some(self.transition_to( - Some(Self::Cooldown { - input: input.clone(), - }), - None, - )) - } + Self::Heartbeating { input } => Some(self.transition_to( + Some(Self::Cooldown { + input: input.clone(), + }), + None, + )), _ => None, } } @@ -236,34 +212,6 @@ impl PresenceState { match self { Self::Heartbeating { input } => Some(self.transition_to( - Some(Self::Reconnecting { - input: input.clone(), - attempts: 1, - reason: reason.clone(), - }), - None, - )), - Self::Reconnecting { - input, attempts, .. - } => Some(self.transition_to( - Some(Self::Reconnecting { - input: input.clone(), - attempts: attempts + 1, - reason: reason.clone(), - }), - None, - )), - _ => None, - } - } - - /// Handle `heartbeat give up` event. - fn presence_heartbeat_give_up_transition( - &self, - reason: &PubNubError, - ) -> Option> { - match self { - Self::Reconnecting { input, .. } => Some(self.transition_to( Some(Self::Failed { input: input.clone(), reason: reason.clone(), @@ -292,7 +240,6 @@ impl PresenceState { match self { Self::Heartbeating { input } | Self::Cooldown { input } - | Self::Reconnecting { input, .. } | Self::Failed { input, .. } => Some(self.transition_to( Some(Self::Stopped { input: input.clone(), @@ -332,15 +279,6 @@ impl State for PresenceState { Self::Cooldown { input } => Some(vec![Wait { input: input.clone(), }]), - Self::Reconnecting { - input, - attempts, - reason, - } => Some(vec![DelayedHeartbeat { - input: input.clone(), - attempts: *attempts, - reason: reason.clone(), - }]), _ => None, } } @@ -348,7 +286,6 @@ impl State for PresenceState { fn exit(&self) -> Option> { match self { PresenceState::Cooldown { .. } => Some(vec![CancelWait]), - PresenceState::Reconnecting { .. } => Some(vec![CancelDelayedHeartbeat]), _ => None, } } @@ -375,9 +312,6 @@ impl State for PresenceState { PresenceEvent::HeartbeatFailure { reason } => { self.presence_heartbeat_failed_transition(reason) } - PresenceEvent::HeartbeatGiveUp { reason } => { - self.presence_heartbeat_give_up_transition(reason) - } PresenceEvent::Reconnect => self.presence_reconnect_transition(), PresenceEvent::Disconnect => self.presence_disconnect_transition(), PresenceEvent::TimesUp => self.presence_times_up_transition(), @@ -412,7 +346,7 @@ mod it_should { use crate::presence::event_engine::effects::LeaveEffectExecutor; use crate::presence::LeaveResult; use crate::{ - core::{event_engine::EventEngine, RequestRetryConfiguration}, + core::event_engine::EventEngine, lib::alloc::sync::Arc, presence::{ event_engine::{ @@ -429,8 +363,6 @@ mod it_should { fn event_engine(start_state: PresenceState) -> Arc { let heartbeat_call: Arc = Arc::new(|_| async move { Ok(HeartbeatResult) }.boxed()); - let delayed_heartbeat_call: Arc = - Arc::new(|_| async move { Ok(HeartbeatResult) }.boxed()); let leave_call: Arc = Arc::new(|_| async move { Ok(LeaveResult) }.boxed()); let wait_call: Arc = Arc::new(|_| async move { Ok(()) }.boxed()); @@ -438,14 +370,7 @@ mod it_should { let (tx, _) = async_channel::bounded(1); EventEngine::new( - PresenceEffectHandler::new( - heartbeat_call, - delayed_heartbeat_call, - leave_call, - wait_call, - RequestRetryConfiguration::None, - tx, - ), + PresenceEffectHandler::new(heartbeat_call, leave_call, wait_call, tx), start_state, RuntimeTokio, ) @@ -581,12 +506,11 @@ mod it_should { PresenceEvent::HeartbeatFailure { reason: PubNubError::Transport { details: "Test".to_string(), response: None } }, - PresenceState::Reconnecting { + PresenceState::Failed { input: PresenceInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), - attempts: 1, reason: PubNubError::Transport { details: "Test".to_string(), response: None } }; "to reconnect on heartbeat failure" @@ -660,24 +584,6 @@ mod it_should { }; "to not change on left with unknown channels and groups" )] - #[test_case( - PresenceState::Heartbeating { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ) - }, - PresenceEvent::HeartbeatGiveUp { - reason: PubNubError::Transport { details: "Test".to_string(), response: None } - }, - PresenceState::Heartbeating { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ) - }; - "to not change on unexpected event" - )] #[tokio::test] async fn transition_for_heartbeating_state( init_state: PresenceState, @@ -841,7 +747,7 @@ mod it_should { &Some(vec!["gr1".to_string()]) ) }, - PresenceEvent::HeartbeatGiveUp { + PresenceEvent::HeartbeatFailure { reason: PubNubError::Transport { details: "Test".to_string(), response: None } }, PresenceState::Cooldown { @@ -868,245 +774,6 @@ mod it_should { assert_eq!(engine.current_state(), target_state); } - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::HeartbeatFailure { - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }, - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 2, - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }; - "to heartbeat reconnecting on heartbeat failure" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::Joined { - heartbeat_interval: 10, - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - }, - PresenceState::Heartbeating { - input: PresenceInput::new( - &Some(vec!["ch1".to_string(), "ch2".to_string()]), - &Some(vec!["gr1".to_string(), "gr2".to_string()]) - ) - }; - "to heartbeating on joined" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string(), "ch2".to_string()]), - &Some(vec!["gr1".to_string(), "gr2".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::Left { - suppress_leave_events: false, - channels: Some(vec!["ch1".to_string()]), - channel_groups: None, - }, - PresenceState::Heartbeating { - input: PresenceInput::new( - &Some(vec!["ch2".to_string()]), - &Some(vec!["gr1".to_string(), "gr2".to_string()]) - ) - }; - "to heartbeating on left" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string(), "ch2".to_string()]), - &Some(vec!["gr1".to_string(), "gr2".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::Left { - suppress_leave_events: false, - channels: Some(vec!["ch1".to_string(), "ch2".to_string()]), - channel_groups: Some(vec!["gr1".to_string(), "gr2".to_string()]), - }, - PresenceState::Inactive; - "to inactive on left for all channels and groups" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::HeartbeatSuccess, - PresenceState::Cooldown { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ) - }; - "to cool down on heartbeat success" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::HeartbeatGiveUp { - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }, - PresenceState::Failed { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }; - "to failed on heartbeat give up" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::Disconnect, - PresenceState::Stopped { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ) - }; - "to stopped on disconnect" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::LeftAll { - suppress_leave_events: false, - }, - PresenceState::Inactive; - "to inactive on left all" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::Joined { - heartbeat_interval: 10, - channels: Some(vec!["ch1".to_string()]), - channel_groups: Some(vec!["gr1".to_string()]), - }, - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }; - "to not change on joined with same channels and groups" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::Left { - suppress_leave_events: false, - channels: None, - channel_groups: Some(vec!["gr3".to_string()]), - }, - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }; - "to not change on left with unknown channels and groups" - )] - #[test_case( - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - PresenceEvent::Reconnect, - PresenceState::Reconnecting { - input: PresenceInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }; - "to not change on unexpected event" - )] - #[tokio::test] - async fn transition_for_reconnecting_state( - init_state: PresenceState, - event: PresenceEvent, - target_state: PresenceState, - ) { - let engine = event_engine(init_state.clone()); - assert!(matches!(init_state, PresenceState::Reconnecting { .. })); - assert_eq!(engine.current_state(), init_state); - - // Process event. - engine.process(&event); - - assert_eq!(engine.current_state(), target_state); - } - #[test_case( PresenceState::Stopped { input: PresenceInput::new( diff --git a/src/dx/presence/event_engine/types.rs b/src/dx/presence/event_engine/types.rs index edf01f2d..44b08b32 100644 --- a/src/dx/presence/event_engine/types.rs +++ b/src/dx/presence/event_engine/types.rs @@ -4,12 +4,9 @@ //! user-provided channels and groups for which `user_id` presence should be //! managed. -use crate::{ - core::PubNubError, - lib::{ - alloc::{collections::HashSet, string::String, vec::Vec}, - core::ops::{Add, Sub}, - }, +use crate::lib::{ + alloc::{collections::HashSet, string::String, vec::Vec}, + core::ops::{Add, Sub}, }; /// User-provided channels and groups for presence. @@ -47,8 +44,8 @@ impl PresenceInput { }) }); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); Self { channels, @@ -99,8 +96,8 @@ impl Add for PresenceInput { fn add(self, rhs: Self) -> Self::Output { let channel_groups = self.join_sets(&self.channel_groups, &rhs.channel_groups); let channels = self.join_sets(&self.channels, &rhs.channels); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); Self { channels, @@ -116,8 +113,8 @@ impl Sub for PresenceInput { fn sub(self, rhs: Self) -> Self::Output { let channel_groups = self.sub_sets(&self.channel_groups, &rhs.channel_groups); let channels = self.sub_sets(&self.channels, &rhs.channels); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); Self { channels, @@ -139,17 +136,6 @@ pub struct PresenceParameters<'execution> { /// List of channel groups for which `user_id` presence should be announced. pub channel_groups: &'execution Option>, - - /// How many consequent retry attempts has been made. - pub attempt: u8, - - /// Reason why previous request created by presence event engine failed. - pub reason: Option, - - /// Effect identifier. - /// - /// Identifier of effect which requested to create request. - pub effect_id: &'execution str, } #[cfg(test)] diff --git a/src/dx/presence/mod.rs b/src/dx/presence/mod.rs index b0baeb76..5ab0aed6 100644 --- a/src/dx/presence/mod.rs +++ b/src/dx/presence/mod.rs @@ -407,6 +407,8 @@ impl PubNubClientInstance { /// .channels(["lobby".into()]) /// .include_state(true) /// .include_user_id(true) + /// .limit(200) + /// .offset(400) /// .execute() /// .await?; /// @@ -556,16 +558,11 @@ where fn presence_event_engine(&self) -> Arc { let channel_bound = 3; let (cancel_tx, cancel_rx) = async_channel::bounded::(channel_bound); - let delayed_heartbeat_cancel_rx = cancel_rx.clone(); let wait_cancel_rx = cancel_rx.clone(); let runtime = self.runtime.clone(); - let delayed_heartbeat_call_client = self.clone(); let heartbeat_call_client = self.clone(); let leave_call_client = self.clone(); let wait_call_client = self.clone(); - let request_retry = self.config.transport.retry_configuration.clone(); - let request_delayed_retry = request_retry.clone(); - let delayed_heartbeat_runtime_sleep = runtime.clone(); let wait_runtime_sleep = runtime.clone(); EventEngine::new( @@ -573,30 +570,6 @@ where Arc::new(move |parameters| { Self::heartbeat_call(heartbeat_call_client.clone(), parameters.clone()) }), - Arc::new(move |parameters| { - let delay_in_microseconds = request_delayed_retry.retry_delay( - Some("/v2/presence".to_string()), - ¶meters.attempt, - parameters.reason.as_ref(), - ); - let inner_runtime_sleep = delayed_heartbeat_runtime_sleep.clone(); - - Self::delayed_heartbeat_call( - delayed_heartbeat_call_client.clone(), - parameters.clone(), - Arc::new(move || { - if let Some(delay) = delay_in_microseconds { - inner_runtime_sleep - .clone() - .sleep_microseconds(delay) - .boxed() - } else { - ready(()).boxed() - } - }), - delayed_heartbeat_cancel_rx.clone(), - ) - }), Arc::new(move |parameters| { Self::leave_call(leave_call_client.clone(), parameters.clone()) }), @@ -616,7 +589,6 @@ where wait_cancel_rx.clone(), ) }), - request_retry, cancel_tx, ), PresenceState::Inactive, @@ -638,25 +610,6 @@ where request.execute().boxed() } - /// Call delayed announce of `user_id` presence. - pub(crate) fn delayed_heartbeat_call( - client: Self, - params: PresenceParameters, - delay: Arc, - cancel_rx: async_channel::Receiver, - ) -> BoxFuture<'static, Result> - where - F: Fn() -> BoxFuture<'static, ()> + Send + Sync + 'static, - { - let effect_id = params.effect_id.to_owned(); - let cancel_task = CancellationTask::new(cancel_rx, effect_id); - - client - .heartbeat_request(params) - .execute_with_cancel_and_delay(delay, cancel_task) - .boxed() - } - /// Call announce `leave` for `user_id`. pub(crate) fn leave_call( client: Self, @@ -714,10 +667,6 @@ where if let Some(channel_groups) = params.channel_groups.clone() { request = request.channel_groups(channel_groups); } - // - // if let Some(presence) = self.presence.clone().read().as_ref() { - // request = request.state_serialized(presence.state.clone()) - // } request } @@ -802,6 +751,30 @@ mod it_should { assert!(request.is_err()) } + #[test] + fn not_heartbeat_when_channels_and_groups_are_empty() { + let client = client(true, None); + let request = client.heartbeat().build(); + + assert!(request.is_err()) + } + + #[tokio::test] + async fn heartbeat_channels_is_comma_when_only_channel_groups_provided() { + let transport = MockTransport { + response: None, + request_handler: Some(Box::new(|req| { + assert_eq!(req.path.split('/').collect::>()[6], ","); + })), + }; + + let _ = client(true, Some(transport)) + .heartbeat() + .channel_groups(["test-cg".into()]) + .execute() + .await; + } + #[tokio::test] async fn send_heartbeat() { let client = PubNubClientBuilder::with_reqwest_transport() @@ -834,13 +807,30 @@ mod it_should { } } + #[tokio::test] + async fn send_heartbeat_with_unique_channels_and_groups() { + let transport = MockTransport { + response: None, + request_handler: Some(Box::new(|req| { + assert_eq!(req.path.split('/').collect::>()[6], "channel_a"); + assert_eq!(req.query_parameters["channel-group"], "group_b"); + })), + }; + + let _ = client(true, Some(transport)) + .heartbeat() + .channels(["channel_a".into(), "channel_a".into(), "channel_a".into()]) + .channel_groups(["group_b".into(), "group_b".into(), "group_b".into()]) + .execute() + .await; + } + #[tokio::test] async fn include_state_in_query() { let transport = MockTransport { response: None, request_handler: Some(Box::new(|req| { assert!(req.query_parameters.contains_key("state")); - assert!(req.query_parameters.get("state").is_some()); let state = req.query_parameters.get("state").unwrap(); assert!(state.contains("channel_a")); @@ -876,4 +866,44 @@ mod it_should { .execute() .await; } + + #[tokio::test] + async fn send_here_now_default_limit() { + let transport = MockTransport { + response: None, + request_handler: Some(Box::new(|req| { + assert_eq!(req.query_parameters["limit"], "1000"); + assert!(!req.query_parameters.contains_key("offset")); + })), + }; + + let _ = client(true, Some(transport)) + .here_now() + .channels(["test-ch".into()]) + .offset(0) + .execute() + .await; + } + + #[tokio::test] + async fn send_here_now_with_too_large_limit() { + let presence_client = PubNubClientBuilder::with_reqwest_transport() + .with_keyset(Keyset { + subscribe_key: "demo", + publish_key: None, + secret_key: None, + }) + .with_user_id("user") + .build() + .unwrap(); + + let response = presence_client + .here_now() + .channels(["test-ch".into()]) + .limit(10000) + .execute() + .await; + + assert!(response.is_err()); + } } diff --git a/src/dx/publish/mod.rs b/src/dx/publish/mod.rs index 28378ad7..c6961809 100644 --- a/src/dx/publish/mod.rs +++ b/src/dx/publish/mod.rs @@ -423,6 +423,7 @@ mod should { struct MockTransport; fn client() -> PubNubClientInstance, DeserializerSerde> { + #[allow(non_local_definitions)] #[async_trait::async_trait] impl Transport for MockTransport { async fn send( @@ -450,6 +451,7 @@ mod should { #[tokio::test] async fn publish_message() { + #[allow(dead_code)] #[derive(Default, Clone)] struct MockTransport; diff --git a/src/dx/pubnub_client.rs b/src/dx/pubnub_client.rs index dd2650d1..4cfc1bf4 100644 --- a/src/dx/pubnub_client.rs +++ b/src/dx/pubnub_client.rs @@ -21,8 +21,6 @@ use crate::providers::futures_tokio::RuntimeTokio; #[cfg(all(any(feature = "subscribe", feature = "presence"), feature = "std"))] use crate::subscribe::{EventDispatcher, SubscriptionCursor, SubscriptionManager}; -#[cfg(feature = "presence")] -use crate::lib::alloc::vec::Vec; #[cfg(all(feature = "presence", feature = "std"))] use crate::presence::PresenceManager; @@ -35,7 +33,10 @@ use crate::transport::TransportReqwest; // TODO: Retry policy would be implemented for `no_std` event engine #[cfg(feature = "std")] -use crate::core::{runtime::RuntimeSupport, RequestRetryConfiguration}; +use crate::{ + core::{retry_policy::Endpoint, runtime::RuntimeSupport, RequestRetryConfiguration}, + lib::alloc::vec, +}; use crate::{ core::{CryptoProvider, PubNubEntity, PubNubError}, @@ -45,6 +46,7 @@ use crate::{ format, string::{String, ToString}, sync::Arc, + vec::Vec, }, collections::HashMap, core::{ @@ -1137,7 +1139,21 @@ impl Default for TransportConfiguration { Self { subscribe_request_timeout: 310, request_timeout: 10, - retry_configuration: RequestRetryConfiguration::None, + retry_configuration: RequestRetryConfiguration::Exponential { + min_delay: 2, + max_delay: 150, + max_retry: 6, + excluded_endpoints: Some(vec![ + Endpoint::MessageSend, + Endpoint::Presence, + Endpoint::Files, + Endpoint::MessageStorage, + Endpoint::ChannelGroups, + Endpoint::DevicePushNotifications, + Endpoint::AppContext, + Endpoint::MessageReactions, + ]), + }, } } } diff --git a/src/dx/subscribe/builders/raw.rs b/src/dx/subscribe/builders/raw.rs index 06ad01ae..838adf3e 100644 --- a/src/dx/subscribe/builders/raw.rs +++ b/src/dx/subscribe/builders/raw.rs @@ -48,24 +48,24 @@ pub struct RawSubscription { #[builder(field(vis = "pub(in crate::dx::subscribe)"), setter(custom))] pub(in crate::dx::subscribe) pubnub_client: PubNubClientInstance, - /// Channels from which real-time updates should be received. + /// Channel(s) from which real-time updates should be received. /// /// List of channels on which [`PubNubClient`] will subscribe and notify /// about received real-time updates. #[builder( field(vis = "pub(in crate::dx::subscribe)"), - setter(into, strip_option), + setter(custom, strip_option), default = "Vec::new()" )] pub(in crate::dx::subscribe) channels: Vec, - /// Channel groups from which real-time updates should be received. + /// Channel group(s) from which real-time updates should be received. /// /// List of groups of channels on which [`PubNubClient`] will subscribe and /// notify about received real-time updates. #[builder( field(vis = "pub(in crate::dx::subscribe)"), - setter(into, strip_option), + setter(custom, strip_option), default = "Vec::new()" )] pub(in crate::dx::subscribe) channel_groups: Vec, @@ -110,6 +110,32 @@ pub struct RawSubscription { } impl RawSubscriptionBuilder { + /// Channel(s) from which real-time updates should be received. + pub fn channels(mut self, channels: L) -> Self + where + L: Into>, + { + let mut unique = channels.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channels = Some(unique); + self + } + + /// Channel group(s) from which real-time updates should be received. + pub fn channel_groups(mut self, channel_groups: L) -> Self + where + L: Into>, + { + let mut unique = channel_groups.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channel_groups = Some(unique); + self + } + /// Validate user-provided data for request builder. /// /// Validator ensure that list of provided data is enough to build valid @@ -146,6 +172,7 @@ where } } +#[cfg(feature = "blocking")] impl RawSubscriptionBuilder where T: blocking::Transport, @@ -226,6 +253,7 @@ where } } +#[cfg(feature = "blocking")] impl RawSubscription where T: blocking::Transport, @@ -255,6 +283,7 @@ where } } +#[cfg(feature = "blocking")] impl Iterator for RawSubscriptionIter where T: blocking::Transport, diff --git a/src/dx/subscribe/builders/subscribe.rs b/src/dx/subscribe/builders/subscribe.rs index 0bfc769e..39b1d62b 100644 --- a/src/dx/subscribe/builders/subscribe.rs +++ b/src/dx/subscribe/builders/subscribe.rs @@ -5,10 +5,7 @@ use derive_builder::Builder; #[cfg(feature = "std")] -use futures::{ - future::BoxFuture, - {select_biased, FutureExt}, -}; +use futures::{select_biased, FutureExt}; use crate::{ core::{ @@ -33,10 +30,10 @@ use crate::{ }, }; +#[cfg(feature = "std")] +use crate::core::event_engine::cancel::CancellationTask; #[cfg(all(feature = "presence", feature = "std"))] use crate::lib::alloc::vec; -#[cfg(feature = "std")] -use crate::{core::event_engine::cancel::CancellationTask, lib::alloc::sync::Arc}; /// The [`SubscribeRequestBuilder`] is used to build subscribe request which /// will be used for real-time updates notification from the [`PubNub`] network. @@ -63,7 +60,11 @@ pub(crate) struct SubscribeRequest { /// /// List of channels on which [`PubNubClient`] will subscribe and notify /// about received real-time updates. - #[builder(field(vis = "pub(in crate::dx::subscribe)"), default = "Vec::new()")] + #[builder( + field(vis = "pub(in crate::dx::subscribe)"), + setter(custom, strip_option), + default = "Vec::new()" + )] pub(in crate::dx::subscribe) channels: Vec, /// Channel groups from which real-time updates should be received. @@ -72,7 +73,7 @@ pub(crate) struct SubscribeRequest { /// notify about received real-time updates. #[builder( field(vis = "pub(in crate::dx::subscribe)"), - setter(strip_option), + setter(custom, strip_option), default = "Vec::new()" )] pub(in crate::dx::subscribe) channel_groups: Vec, @@ -146,6 +147,32 @@ pub(crate) struct SubscribeRequest { } impl SubscribeRequestBuilder { + /// Channel(s) from which real-time updates should be received. + pub fn channels(mut self, channels: L) -> Self + where + L: Into>, + { + let mut unique = channels.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channels = Some(unique); + self + } + + /// Channel group(s) from which real-time updates should be received. + pub fn channel_groups(mut self, channel_groups: L) -> Self + where + L: Into>, + { + let mut unique = channel_groups.into(); + unique.sort_unstable(); + unique.dedup(); + + self.channel_groups = Some(unique); + self + } + /// A state that should be associated with the `user_id`. /// /// `state` object should be a `HashMap` with channel names as keys and @@ -265,39 +292,23 @@ where .await } - /// Build and call asynchronous request after delay. + /// Build and call asynchronous request with cancellation ability. /// /// Perform delayed request call with ability to cancel it before call. #[cfg(feature = "std")] - pub async fn execute_with_cancel_and_delay( + pub async fn execute_with_cancel( self, - delay: Arc, cancel_task: CancellationTask, - ) -> Result - where - F: Fn() -> BoxFuture<'static, ()> + Send + Sync + 'static, - { + ) -> Result { select_biased! { _ = cancel_task.wait_for_cancel().fuse() => { Err(PubNubError::EffectCanceled) }, - response = self.execute_with_delay(delay).fuse() => { + response = self.execute().fuse() => { response } } } - - /// Build and call asynchronous request after configured delay. - #[cfg(feature = "std")] - async fn execute_with_delay(self, delay: Arc) -> Result - where - F: Fn() -> BoxFuture<'static, ()> + Send + Sync + 'static, - { - // Postpone request execution. - delay().await; - - self.execute().await - } } impl SubscribeRequestBuilder @@ -325,7 +336,6 @@ where mod should { use super::*; use crate::{core::TransportResponse, PubNubClientBuilder}; - use futures::future::ready; #[tokio::test] async fn be_able_to_cancel_subscribe_call() { @@ -357,7 +367,7 @@ mod should { .unwrap() .subscribe_request() .channels(vec!["test".into()]) - .execute_with_cancel_and_delay(Arc::new(|| ready(()).boxed()), cancel_task) + .execute_with_cancel(cancel_task) .await; assert!(matches!(result, Err(PubNubError::EffectCanceled))); diff --git a/src/dx/subscribe/event_dispatcher.rs b/src/dx/subscribe/event_dispatcher.rs index bb2007a5..697d8f16 100644 --- a/src/dx/subscribe/event_dispatcher.rs +++ b/src/dx/subscribe/event_dispatcher.rs @@ -251,9 +251,8 @@ impl EventDispatcher { /// # Arguments /// /// * `condition` - A closure that determines whether an event matches the - /// condition. - /// It should accept a reference to a `SubscribeStreamEvent` and return a - /// `bool`. + /// condition. It should accept a reference to a `SubscribeStreamEvent` + /// and return a `bool`. /// /// # Returns /// @@ -432,6 +431,7 @@ mod it_should { data: "Test message 1".to_string().into_bytes(), r#type: None, space_id: None, + user_metadata: None, decryption_error: None, }), Update::Signal(Message { @@ -442,6 +442,7 @@ mod it_should { data: "Test signal 1".to_string().into_bytes(), r#type: None, space_id: None, + user_metadata: None, decryption_error: None, }), Update::Presence(Presence::Join { @@ -461,6 +462,7 @@ mod it_should { data: "Test message 2".to_string().into_bytes(), r#type: None, space_id: None, + user_metadata: None, decryption_error: None, }), ] diff --git a/src/dx/subscribe/event_engine/effect_handler.rs b/src/dx/subscribe/event_engine/effect_handler.rs index 462512ae..329ab753 100644 --- a/src/dx/subscribe/event_engine/effect_handler.rs +++ b/src/dx/subscribe/event_engine/effect_handler.rs @@ -2,7 +2,6 @@ use async_channel::Sender; use spin::rwlock::RwLock; use uuid::Uuid; -use crate::core::RequestRetryConfiguration; use crate::{ core::event_engine::EffectHandler, dx::subscribe::event_engine::{ @@ -29,9 +28,6 @@ pub(crate) struct SubscribeEffectHandler { /// Emit messages function pointer. emit_messages: Arc, - /// Retry policy. - retry_policy: RequestRetryConfiguration, - /// Cancellation channel. cancellation_channel: Sender, } @@ -42,14 +38,12 @@ impl SubscribeEffectHandler { subscribe_call: Arc, emit_status: Arc, emit_messages: Arc, - retry_policy: RequestRetryConfiguration, cancellation_channel: Sender, ) -> Self { Self { subscribe_call, emit_status, emit_messages, - retry_policy, cancellation_channel, } } @@ -68,22 +62,6 @@ impl EffectHandler for SubscribeEffe cancellation_channel: self.cancellation_channel.clone(), }) } - SubscribeEffectInvocation::HandshakeReconnect { - input, - cursor, - attempts, - reason, - } => Some(SubscribeEffect::HandshakeReconnect { - id: Uuid::new_v4().to_string(), - cancelled: RwLock::new(false), - input: input.clone(), - cursor: cursor.clone(), - attempts: *attempts, - reason: reason.clone(), - retry_policy: self.retry_policy.clone(), - executor: self.subscribe_call.clone(), - cancellation_channel: self.cancellation_channel.clone(), - }), SubscribeEffectInvocation::Receive { input, cursor } => { Some(SubscribeEffect::Receive { id: Uuid::new_v4().to_string(), @@ -94,22 +72,6 @@ impl EffectHandler for SubscribeEffe cancellation_channel: self.cancellation_channel.clone(), }) } - SubscribeEffectInvocation::ReceiveReconnect { - input, - cursor, - attempts, - reason, - } => Some(SubscribeEffect::ReceiveReconnect { - id: Uuid::new_v4().to_string(), - cancelled: RwLock::new(false), - input: input.clone(), - cursor: cursor.clone(), - attempts: *attempts, - reason: reason.clone(), - retry_policy: self.retry_policy.clone(), - executor: self.subscribe_call.clone(), - cancellation_channel: self.cancellation_channel.clone(), - }), SubscribeEffectInvocation::EmitStatus(status) => Some(SubscribeEffect::EmitStatus { id: Uuid::new_v4().to_string(), status: status.clone(), diff --git a/src/dx/subscribe/event_engine/effects/emit_messages.rs b/src/dx/subscribe/event_engine/effects/emit_messages.rs index a8c542c6..bd3e8b3f 100644 --- a/src/dx/subscribe/event_engine/effects/emit_messages.rs +++ b/src/dx/subscribe/event_engine/effects/emit_messages.rs @@ -36,6 +36,7 @@ mod should { data: vec![], r#type: None, space_id: None, + user_metadata: None, decryption_error: None, }; diff --git a/src/dx/subscribe/event_engine/effects/handshake.rs b/src/dx/subscribe/event_engine/effects/handshake.rs index c0fab74b..8bb2db44 100644 --- a/src/dx/subscribe/event_engine/effects/handshake.rs +++ b/src/dx/subscribe/event_engine/effects/handshake.rs @@ -30,8 +30,6 @@ pub(super) async fn execute( channels: &input.channels(), channel_groups: &input.channel_groups(), cursor: None, - attempt: 0, - reason: None, effect_id, }) .map_ok_or_else( @@ -71,8 +69,6 @@ mod should { assert_eq!(params.channels, &Some(vec!["ch1".to_string()])); assert_eq!(params.channel_groups, &Some(vec!["cg1".to_string()])); assert_eq!(params.cursor, None); - assert_eq!(params.attempt, 0); - assert_eq!(params.reason, None); assert_eq!(params.effect_id, "id"); async move { diff --git a/src/dx/subscribe/event_engine/effects/handshake_reconnection.rs b/src/dx/subscribe/event_engine/effects/handshake_reconnection.rs deleted file mode 100644 index 17651655..00000000 --- a/src/dx/subscribe/event_engine/effects/handshake_reconnection.rs +++ /dev/null @@ -1,251 +0,0 @@ -use futures::TryFutureExt; -use log::info; - -use crate::{ - core::{PubNubError, RequestRetryConfiguration}, - dx::subscribe::{ - event_engine::{ - effects::SubscribeEffectExecutor, SubscribeEvent, SubscriptionInput, SubscriptionParams, - }, - SubscriptionCursor, - }, - lib::alloc::{sync::Arc, vec, vec::Vec}, -}; - -pub(super) async fn execute( - input: &SubscriptionInput, - cursor: &Option, - attempt: u8, - reason: PubNubError, - effect_id: &str, - retry_policy: &RequestRetryConfiguration, - executor: &Arc, -) -> Vec { - if !matches!(reason, PubNubError::EffectCanceled) - && !retry_policy.retriable(Some("/v2/subscribe"), &attempt, Some(&reason)) - { - return vec![SubscribeEvent::HandshakeReconnectGiveUp { reason }]; - } - - info!( - "Handshake reconnection for\nchannels: {:?}\nchannel groups: {:?}", - input.channels().unwrap_or_default(), - input.channel_groups().unwrap_or_default() - ); - - if input.is_empty { - return vec![SubscribeEvent::UnsubscribeAll]; - } - - executor(SubscriptionParams { - channels: &input.channels(), - channel_groups: &input.channel_groups(), - cursor: None, - attempt, - reason: Some(reason), - effect_id, - }) - .map_ok_or_else( - |error| { - log::error!("Handshake reconnection error: {:?}", error); - - (!matches!(error, PubNubError::EffectCanceled)) - .then(|| vec![SubscribeEvent::HandshakeReconnectFailure { reason: error }]) - .unwrap_or(vec![]) - }, - |subscribe_result| { - let cursor = { - if cursor.is_none() { - subscribe_result.cursor - } else { - let mut cursor = cursor.clone().unwrap_or_default(); - cursor.region = subscribe_result.cursor.region; - cursor - } - }; - vec![SubscribeEvent::HandshakeReconnectSuccess { cursor }] - }, - ) - .await -} - -#[cfg(test)] -mod should { - use super::*; - use crate::core::TransportResponse; - use crate::{core::PubNubError, dx::subscribe::result::SubscribeResult}; - use futures::FutureExt; - - #[tokio::test] - async fn initialize_handshake_reconnect_attempt() { - let mock_handshake_function: Arc = Arc::new(move |params| { - assert_eq!(params.channels, &Some(vec!["ch1".to_string()])); - assert_eq!(params.channel_groups, &Some(vec!["cg1".to_string()])); - assert_eq!(params.cursor, None); - assert_eq!(params.attempt, 1); - assert_eq!( - params.reason.unwrap(), - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })) - } - ); - assert_eq!(params.effect_id, "id"); - - async move { - Ok(SubscribeResult { - cursor: Default::default(), - messages: vec![], - }) - } - .boxed() - }); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &None, - 1, - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }, - "id", - &RequestRetryConfiguration::Linear { - delay: 0, - max_retry: 1, - excluded_endpoints: None, - }, - &mock_handshake_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - SubscribeEvent::HandshakeReconnectSuccess { .. } - )); - } - - #[tokio::test] - async fn return_handshake_reconnect_give_up_event_on_err() { - let mock_handshake_function: Arc = Arc::new(move |_| { - async move { - Err(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - } - .boxed() - }); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &None, - 11, - PubNubError::Transport { - details: "test".into(), - response: None, - }, - "id", - &RequestRetryConfiguration::Linear { - max_retry: 10, - delay: 0, - excluded_endpoints: None, - }, - &mock_handshake_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - SubscribeEvent::HandshakeReconnectGiveUp { .. } - )); - } - - #[tokio::test] - async fn return_empty_event_on_effect_cancel_err() { - let mock_handshake_function: Arc = - Arc::new(move |_| async move { Err(PubNubError::EffectCanceled) }.boxed()); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &None, - 1, - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }, - "id", - &RequestRetryConfiguration::Linear { - delay: 0, - max_retry: 1, - excluded_endpoints: None, - }, - &mock_handshake_function, - ) - .await; - - assert!(result.is_empty()); - } - - #[tokio::test] - async fn return_handshake_reconnect_give_up_event_on_err_with_none_auto_retry_policy() { - let mock_handshake_function: Arc = Arc::new(move |_| { - async move { - Err(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - } - .boxed() - }); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &None, - 1, - PubNubError::Transport { - details: "test".into(), - response: None, - }, - "id", - &RequestRetryConfiguration::None, - &mock_handshake_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - SubscribeEvent::HandshakeReconnectGiveUp { .. } - )); - } -} diff --git a/src/dx/subscribe/event_engine/effects/mod.rs b/src/dx/subscribe/event_engine/effects/mod.rs index f5893c97..5cc79827 100644 --- a/src/dx/subscribe/event_engine/effects/mod.rs +++ b/src/dx/subscribe/event_engine/effects/mod.rs @@ -5,7 +5,7 @@ use futures::future::BoxFuture; use spin::RwLock; use crate::{ - core::{event_engine::Effect, PubNubError, RequestRetryConfiguration}, + core::{event_engine::Effect, PubNubError}, dx::subscribe::{ event_engine::{ types::{SubscriptionInput, SubscriptionParams}, @@ -23,9 +23,7 @@ use crate::{ mod emit_messages; mod emit_status; mod handshake; -mod handshake_reconnection; mod receive; -mod receive_reconnection; /// `SubscribeEffectExecutor` is a trait alias representing a type that executes /// subscribe effects. @@ -92,48 +90,6 @@ pub(crate) enum SubscribeEffect { cancellation_channel: Sender, }, - /// Retry initial subscribe effect invocation. - HandshakeReconnect { - /// Unique effect identifier. - id: String, - - /// Whether handshake reconnect effect has been cancelled or not. - cancelled: RwLock, - - /// User input with channels and groups. - /// - /// Object contains list of channels and channel groups which has been - /// used during recently failed initial subscription. - input: SubscriptionInput, - - /// Time cursor. - /// - /// Cursor used by subscription loop to identify point in time after - /// which updates will be delivered. - cursor: Option, - - /// Current initial subscribe retry attempt. - /// - /// Used to track overall number of initial subscription retry attempts. - attempts: u8, - - /// Initial subscribe attempt failure reason. - reason: PubNubError, - - /// Retry policy. - retry_policy: RequestRetryConfiguration, - - /// Executor function. - /// - /// Function which will be used to execute initial subscription. - executor: Arc, - - /// Cancellation channel. - /// - /// Channel which will be used to cancel effect execution. - cancellation_channel: Sender, - }, - /// Receive updates effect invocation. Receive { /// Unique effect identifier. @@ -165,48 +121,6 @@ pub(crate) enum SubscribeEffect { cancellation_channel: Sender, }, - /// Retry receive updates effect invocation. - ReceiveReconnect { - /// Unique effect identifier. - id: String, - - /// Whether receive reconnect effect has been cancelled or not. - cancelled: RwLock, - - /// User input with channels and groups. - /// - /// Object contains list of channels and channel groups which has been - /// used during recently failed receive updates. - input: SubscriptionInput, - - /// Time cursor. - /// - /// Cursor used by subscription loop to identify point in time after - /// which updates will be delivered. - cursor: SubscriptionCursor, - - /// Current receive retry attempt. - /// - /// Used to track overall number of receive updates retry attempts. - attempts: u8, - - /// Receive updates attempt failure reason. - reason: PubNubError, - - /// Retry policy. - retry_policy: RequestRetryConfiguration, - - /// Executor function. - /// - /// Function which will be used to execute receive updates. - executor: Arc, - - /// Cancellation channel. - /// - /// Channel which will be used to cancel effect execution. - cancellation_channel: Sender, - }, - /// Status change notification effect invocation. EmitStatus { /// Unique effect identifier. @@ -250,18 +164,6 @@ impl Debug for SubscribeEffect { input.channels(), input.channel_groups() ), - Self::HandshakeReconnect { - input, - attempts, - reason, - .. - } => write!( - f, - "SubscribeEffect::HandshakeReconnect {{ channels: {:?}, channel groups: {:?}, \ - attempts: {attempts:?}, reason: {reason:?} }}", - input.channels(), - input.channel_groups() - ), Self::Receive { input, cursor, .. } => write!( f, "SubscribeEffect::Receive {{ channels: {:?}, channel groups: {:?}, cursor: \ @@ -269,18 +171,6 @@ impl Debug for SubscribeEffect { input.channels(), input.channel_groups() ), - Self::ReceiveReconnect { - input, - attempts, - reason, - .. - } => write!( - f, - "SubscribeEffect::ReceiveReconnect {{ channels: {:?}, channel groups: {:?}, \ - attempts: {attempts:?}, reason: {reason:?} }}", - input.channels(), - input.channel_groups() - ), Self::EmitStatus { status, .. } => { write!(f, "SubscribeEffect::EmitStatus {{ status: {status:?} }}") } @@ -301,9 +191,7 @@ impl Effect for SubscribeEffect { fn name(&self) -> String { match self { Self::Handshake { .. } => "HANDSHAKE", - Self::HandshakeReconnect { .. } => "HANDSHAKE_RECONNECT", Self::Receive { .. } => "RECEIVE_MESSAGES", - Self::ReceiveReconnect { .. } => "RECEIVE_RECONNECT", Self::EmitStatus { .. } => "EMIT_STATUS", Self::EmitMessages { .. } => "EMIT_MESSAGES", } @@ -313,9 +201,7 @@ impl Effect for SubscribeEffect { fn id(&self) -> String { match self { Self::Handshake { id, .. } - | Self::HandshakeReconnect { id, .. } | Self::Receive { id, .. } - | Self::ReceiveReconnect { id, .. } | Self::EmitStatus { id, .. } | Self::EmitMessages { id, .. } => id, } @@ -331,28 +217,6 @@ impl Effect for SubscribeEffect { executor, .. } => handshake::execute(input, cursor, id, executor).await, - Self::HandshakeReconnect { - id, - input, - cursor, - attempts, - reason, - retry_policy, - executor, - .. - } => { - handshake_reconnection::execute( - input, - cursor, - *attempts, - reason.clone(), /* TODO: Does run function need to borrow self? Or we can - * consume it? */ - id, - retry_policy, - executor, - ) - .await - } Self::Receive { id, input, @@ -360,28 +224,6 @@ impl Effect for SubscribeEffect { executor, .. } => receive::execute(input, cursor, id, executor).await, - Self::ReceiveReconnect { - id, - input, - cursor, - attempts, - reason, - retry_policy, - executor, - .. - } => { - receive_reconnection::execute( - input, - cursor, - *attempts, - reason.clone(), /* TODO: Does run function need to borrow self? Or we can - * consume it? */ - id, - retry_policy, - executor, - ) - .await - } Self::EmitStatus { status, executor, .. } => emit_status::execute(status.clone(), executor).await, @@ -402,23 +244,11 @@ impl Effect for SubscribeEffect { cancellation_channel, .. } - | Self::HandshakeReconnect { - id, - cancelled, - cancellation_channel, - .. - } | Self::Receive { id, cancelled, cancellation_channel, .. - } - | Self::ReceiveReconnect { - id, - cancelled, - cancellation_channel, - .. } => { { let mut cancelled_slot = cancelled.write(); @@ -434,10 +264,9 @@ impl Effect for SubscribeEffect { fn is_cancelled(&self) -> bool { match self { - Self::Handshake { cancelled, .. } - | Self::HandshakeReconnect { cancelled, .. } - | Self::Receive { cancelled, .. } - | Self::ReceiveReconnect { cancelled, .. } => *cancelled.read(), + Self::Handshake { cancelled, .. } | Self::Receive { cancelled, .. } => { + *cancelled.read() + } _ => false, } } diff --git a/src/dx/subscribe/event_engine/effects/receive.rs b/src/dx/subscribe/event_engine/effects/receive.rs index fdd4cd42..7f49b55f 100644 --- a/src/dx/subscribe/event_engine/effects/receive.rs +++ b/src/dx/subscribe/event_engine/effects/receive.rs @@ -34,8 +34,6 @@ pub(crate) async fn execute( channels: &input.channels(), channel_groups: &input.channel_groups(), cursor: Some(cursor), - attempt: 0, - reason: None, effect_id, }) .map_ok_or_else( @@ -67,8 +65,6 @@ mod should { let mock_receive_function: Arc = Arc::new(move |params| { assert_eq!(params.channels, &Some(vec!["ch1".to_string()])); assert_eq!(params.channel_groups, &Some(vec!["cg1".to_string()])); - assert_eq!(params.attempt, 0); - assert_eq!(params.reason, None); assert_eq!(params.cursor, Some(&Default::default())); assert_eq!(params.effect_id, "id"); diff --git a/src/dx/subscribe/event_engine/effects/receive_reconnection.rs b/src/dx/subscribe/event_engine/effects/receive_reconnection.rs deleted file mode 100644 index 5e0d8e36..00000000 --- a/src/dx/subscribe/event_engine/effects/receive_reconnection.rs +++ /dev/null @@ -1,303 +0,0 @@ -use futures::TryFutureExt; -use log::info; - -use crate::{ - core::{PubNubError, RequestRetryConfiguration}, - dx::subscribe::{ - event_engine::{ - effects::SubscribeEffectExecutor, types::SubscriptionParams, SubscribeEvent, - SubscriptionInput, - }, - SubscriptionCursor, - }, - lib::alloc::{sync::Arc, vec, vec::Vec}, -}; - -#[allow(clippy::too_many_arguments)] -pub(crate) async fn execute( - input: &SubscriptionInput, - cursor: &SubscriptionCursor, - attempt: u8, - reason: PubNubError, - effect_id: &str, - retry_policy: &RequestRetryConfiguration, - executor: &Arc, -) -> Vec { - if !matches!(reason, PubNubError::EffectCanceled) - && !retry_policy.retriable(Some("/v2/subscribe"), &attempt, Some(&reason)) - { - return vec![SubscribeEvent::ReceiveReconnectGiveUp { reason }]; - } - - info!( - "Receive reconnection at {:?} for\nchannels: {:?}\nchannel groups: {:?}", - cursor.timetoken, - input.channels().unwrap_or_default(), - input.channel_groups().unwrap_or_default() - ); - - if input.is_empty { - return vec![SubscribeEvent::UnsubscribeAll]; - } - - executor(SubscriptionParams { - channels: &input.channels(), - channel_groups: &input.channel_groups(), - cursor: Some(cursor), - attempt, - reason: Some(reason), - effect_id, - }) - .map_ok_or_else( - |error| { - log::debug!("Receive reconnection error: {:?}", error); - - (!matches!(error, PubNubError::EffectCanceled)) - .then(|| vec![SubscribeEvent::ReceiveReconnectFailure { reason: error }]) - .unwrap_or(vec![]) - }, - |subscribe_result| { - vec![SubscribeEvent::ReceiveReconnectSuccess { - cursor: subscribe_result.cursor, - messages: subscribe_result.messages, - }] - }, - ) - .await -} - -#[cfg(test)] -mod should { - use super::*; - use crate::{ - core::{PubNubError, TransportResponse}, - dx::subscribe::result::SubscribeResult, - lib::alloc::boxed::Box, - }; - use futures::FutureExt; - - #[tokio::test] - async fn receive_reconnect() { - let mock_receive_function: Arc = Arc::new(move |params| { - assert_eq!(params.channels, &Some(vec!["ch1".to_string()])); - assert_eq!(params.channel_groups, &Some(vec!["cg1".to_string()])); - assert_eq!(params.attempt, 10); - assert_eq!( - params.reason, - Some(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - ); - assert_eq!(params.cursor, Some(&Default::default())); - assert_eq!(params.effect_id, "id"); - - async move { - Ok(SubscribeResult { - cursor: Default::default(), - messages: vec![], - }) - } - .boxed() - }); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &Default::default(), - 10, - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }, - "id", - &RequestRetryConfiguration::Linear { - max_retry: 20, - delay: 0, - excluded_endpoints: None, - }, - &mock_receive_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - SubscribeEvent::ReceiveReconnectSuccess { .. } - )); - } - - #[tokio::test] - async fn return_receive_reconnect_failure_event_on_err() { - let mock_receive_function: Arc = Arc::new(move |_| { - async move { - Err(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - } - .boxed() - }); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &Default::default(), - 5, - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }, - "id", - &RequestRetryConfiguration::Linear { - max_retry: 10, - delay: 0, - excluded_endpoints: None, - }, - &mock_receive_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - SubscribeEvent::ReceiveReconnectFailure { .. } - )); - } - - #[tokio::test] - async fn return_receive_reconnect_give_up_event_on_err() { - let mock_receive_function: Arc = Arc::new(move |_| { - async move { - Err(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - } - .boxed() - }); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &Default::default(), - 10, - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }, - "id", - &RequestRetryConfiguration::Linear { - delay: 0, - max_retry: 1, - excluded_endpoints: None, - }, - &mock_receive_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - SubscribeEvent::ReceiveReconnectGiveUp { .. } - )); - } - - #[tokio::test] - async fn return_empty_event_on_effect_cancel_err() { - let mock_receive_function: Arc = - Arc::new(move |_| async move { Err(PubNubError::EffectCanceled) }.boxed()); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &Default::default(), - 10, - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }, - "id", - &RequestRetryConfiguration::Linear { - max_retry: 20, - delay: 0, - excluded_endpoints: None, - }, - &mock_receive_function, - ) - .await; - - assert!(result.is_empty()); - } - - #[tokio::test] - async fn return_receive_reconnect_give_up_event_on_err_with_none_auto_retry_policy() { - let mock_receive_function: Arc = Arc::new(move |_| { - async move { - Err(PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }) - } - .boxed() - }); - - let result = execute( - &SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["cg1".to_string()]), - ), - &Default::default(), - 10, - PubNubError::Transport { - details: "test".into(), - response: Some(Box::new(TransportResponse { - status: 500, - ..Default::default() - })), - }, - "id", - &RequestRetryConfiguration::None, - &mock_receive_function, - ) - .await; - - assert!(!result.is_empty()); - assert!(matches!( - result.first().unwrap(), - SubscribeEvent::ReceiveReconnectGiveUp { .. } - )); - } -} diff --git a/src/dx/subscribe/event_engine/event.rs b/src/dx/subscribe/event_engine/event.rs index bd3ccfd0..97426881 100644 --- a/src/dx/subscribe/event_engine/event.rs +++ b/src/dx/subscribe/event_engine/event.rs @@ -7,7 +7,7 @@ use crate::{ /// Subscription events. /// -/// Subscribe state machine behaviour depends from external events which it +/// Subscribe state machine behaviour depends on from external events which it /// receives. #[derive(Debug)] pub(crate) enum SubscribeEvent { @@ -46,30 +46,6 @@ pub(crate) enum SubscribeEvent { /// [`PubNub`]: https://www.pubnub.com/ HandshakeFailure { reason: PubNubError }, - /// Handshake reconnect completed successfully. - /// - /// Emitted when another handshake attempt was successful and [`PubNub`] - /// network returned timetoken (cursor) which will be used for subscription - /// loop. - /// - /// [`PubNub`]: https://www.pubnub.com/ - HandshakeReconnectSuccess { cursor: SubscriptionCursor }, - - /// Handshake reconnect completed with error. - /// - /// Emitted when another handshake effect attempt was unable to receive - /// response from [`PubNub`] network (network or permissions issues). - /// - /// [`PubNub`]: https://www.pubnub.com/ - HandshakeReconnectFailure { reason: PubNubError }, - - /// All handshake attempts was unsuccessful. - /// - /// Emitted when handshake reconnect attempts reached maximum allowed count - /// (according to retry / reconnection policy) and all following attempts - /// should be stopped. - HandshakeReconnectGiveUp { reason: PubNubError }, - /// Receive updates completed successfully. /// /// Emitted when [`PubNub`] network returned list of real-time updates along @@ -89,34 +65,6 @@ pub(crate) enum SubscribeEvent { /// [`PubNub`]: https://www.pubnub.com/ ReceiveFailure { reason: PubNubError }, - /// Receive updates reconnect completed successfully. - /// - /// Emitted when another receive updates attempt was successful and - /// [`PubNub`] network returned list of real-time updates along - /// timetoken (cursor) which will be used for subscription loop. - /// - /// [`PubNub`]: https://www.pubnub.com/ - ReceiveReconnectSuccess { - cursor: SubscriptionCursor, - messages: Vec, - }, - - /// Receive updates reconnect completed with error. - /// - /// Emitted when another receive updates effect attempt was unable to - /// receive response from [`PubNub`] network (network issues or - /// revoked permissions). - /// - /// [`PubNub`]: https://www.pubnub.com/ - ReceiveReconnectFailure { reason: PubNubError }, - - /// All receive updates attempts was unsuccessful. - /// - /// Emitted when receive updates reconnect attempts reached maximum allowed - /// count (according to retry / reconnection policy) and all following - /// attempts should be stopped. - ReceiveReconnectGiveUp { reason: PubNubError }, - /// Disconnect from [`PubNub`] network. /// /// Emitted when explicitly requested to stop receiving real-time updates. @@ -145,14 +93,8 @@ impl Event for SubscribeEvent { Self::SubscriptionRestored { .. } => "SUBSCRIPTION_RESTORED", Self::HandshakeSuccess { .. } => "HANDSHAKE_SUCCESS", Self::HandshakeFailure { .. } => "HANDSHAKE_FAILURE", - Self::HandshakeReconnectSuccess { .. } => "HANDSHAKE_RECONNECT_SUCCESS", - Self::HandshakeReconnectFailure { .. } => "HANDSHAKE_RECONNECT_FAILURE", - Self::HandshakeReconnectGiveUp { .. } => "HANDSHAKE_RECONNECT_GIVEUP", Self::ReceiveSuccess { .. } => "RECEIVE_SUCCESS", Self::ReceiveFailure { .. } => "RECEIVE_FAILURE", - Self::ReceiveReconnectSuccess { .. } => "RECEIVE_RECONNECT_SUCCESS", - Self::ReceiveReconnectFailure { .. } => "RECEIVE_RECONNECT_FAILURE", - Self::ReceiveReconnectGiveUp { .. } => "RECEIVE_RECONNECT_GIVEUP", Self::Disconnect => "DISCONNECT", Self::Reconnect { .. } => "RECONNECT", Self::UnsubscribeAll => "UNSUBSCRIBE_ALL", diff --git a/src/dx/subscribe/event_engine/invocation.rs b/src/dx/subscribe/event_engine/invocation.rs index 7ff81b48..dfb3ae06 100644 --- a/src/dx/subscribe/event_engine/invocation.rs +++ b/src/dx/subscribe/event_engine/invocation.rs @@ -1,5 +1,5 @@ use crate::{ - core::{event_engine::EffectInvocation, PubNubError}, + core::event_engine::EffectInvocation, dx::subscribe::{ event_engine::{SubscribeEffect, SubscribeEvent, SubscriptionInput}, result::Update, @@ -35,32 +35,6 @@ pub(crate) enum SubscribeEffectInvocation { /// Cancel initial subscribe effect invocation. CancelHandshake, - /// Retry initial subscribe effect invocation. - HandshakeReconnect { - /// User input with channels and groups. - /// - /// Object contains list of channels and groups which has been used - /// during recently failed initial subscription. - input: SubscriptionInput, - - /// Time cursor. - /// - /// Cursor used by subscription loop to identify point in time after - /// which updates will be delivered. - cursor: Option, - - /// Current initial subscribe retry attempt. - /// - /// Used to track overall number of initial subscription retry attempts. - attempts: u8, - - /// Initial subscribe attempt failure reason. - reason: PubNubError, - }, - - /// Cancel initial subscribe retry effect invocation. - CancelHandshakeReconnect, - /// Receive updates effect invocation. Receive { /// User input with channels and groups. @@ -79,32 +53,6 @@ pub(crate) enum SubscribeEffectInvocation { /// Cancel receive updates effect invocation. CancelReceive, - /// Retry receive updates effect invocation. - ReceiveReconnect { - /// User input with channels and groups. - /// - /// Object contains list of channels and groups which has been used - /// during recently failed receive updates. - input: SubscriptionInput, - - /// Time cursor. - /// - /// Cursor used by subscription loop to identify point in time after - /// which updates will be delivered. - cursor: SubscriptionCursor, - - /// Current receive retry attempt. - /// - /// Used to track overall number of receive updates retry attempts. - attempts: u8, - - /// Receive updates attempt failure reason. - reason: PubNubError, - }, - - /// Cancel receive updates retry effect invocation. - CancelReceiveReconnect, - /// Status change notification effect invocation. EmitStatus(ConnectionStatus), @@ -123,12 +71,8 @@ impl EffectInvocation for SubscribeEffectInvocation { match self { Self::Handshake { .. } => "HANDSHAKE", Self::CancelHandshake { .. } => "CANCEL_HANDSHAKE", - Self::HandshakeReconnect { .. } => "HANDSHAKE_RECONNECT", - Self::CancelHandshakeReconnect { .. } => "CANCEL_HANDSHAKE_RECONNECT", Self::Receive { .. } => "RECEIVE_MESSAGES", Self::CancelReceive { .. } => "CANCEL_RECEIVE_MESSAGES", - Self::ReceiveReconnect { .. } => "RECEIVE_RECONNECT", - Self::CancelReceiveReconnect { .. } => "CANCEL_RECEIVE_RECONNECT", Self::EmitStatus(_) => "EMIT_STATUS", Self::EmitMessages(_, _) => "EMIT_MESSAGES", Self::TerminateEventEngine => "TERMINATE_EVENT_ENGINE", @@ -136,34 +80,21 @@ impl EffectInvocation for SubscribeEffectInvocation { } fn is_managed(&self) -> bool { - matches!( - self, - Self::Handshake { .. } - | Self::HandshakeReconnect { .. } - | Self::Receive { .. } - | Self::ReceiveReconnect { .. } - ) + matches!(self, Self::Handshake { .. } | Self::Receive { .. }) } fn is_cancelling(&self) -> bool { matches!( self, - Self::CancelHandshake { .. } - | Self::CancelHandshakeReconnect { .. } - | Self::CancelReceive { .. } - | Self::CancelReceiveReconnect + Self::CancelHandshake { .. } | Self::CancelReceive { .. } ) } fn cancelling_effect(&self, effect: &Self::Effect) -> bool { (matches!(effect, SubscribeEffect::Handshake { .. }) && matches!(self, Self::CancelHandshake { .. })) - || (matches!(effect, SubscribeEffect::HandshakeReconnect { .. }) - && matches!(self, Self::CancelHandshakeReconnect { .. })) || (matches!(effect, SubscribeEffect::Receive { .. }) && matches!(self, Self::CancelReceive { .. })) - || (matches!(effect, SubscribeEffect::ReceiveReconnect { .. }) - && matches!(self, Self::CancelReceiveReconnect { .. })) } fn is_terminating(&self) -> bool { @@ -176,12 +107,8 @@ impl Display for SubscribeEffectInvocation { match self { Self::Handshake { .. } => write!(f, "HANDSHAKE"), Self::CancelHandshake => write!(f, "CANCEL_HANDSHAKE"), - Self::HandshakeReconnect { .. } => write!(f, "HANDSHAKE_RECONNECT"), - Self::CancelHandshakeReconnect => write!(f, "CANCEL_HANDSHAKE_RECONNECT"), Self::Receive { .. } => write!(f, "RECEIVE_MESSAGES"), Self::CancelReceive { .. } => write!(f, "CANCEL_RECEIVE_MESSAGES"), - Self::ReceiveReconnect { .. } => write!(f, "RECEIVE_RECONNECT"), - Self::CancelReceiveReconnect { .. } => write!(f, "CANCEL_RECEIVE_RECONNECT"), Self::EmitStatus(status) => write!(f, "EMIT_STATUS({status:?})"), Self::EmitMessages(messages, _) => write!(f, "EMIT_MESSAGES({messages:?})"), Self::TerminateEventEngine => write!(f, "TERMINATE_EVENT_ENGINE"), diff --git a/src/dx/subscribe/event_engine/state.rs b/src/dx/subscribe/event_engine/state.rs index f5debe80..567a081e 100644 --- a/src/dx/subscribe/event_engine/state.rs +++ b/src/dx/subscribe/event_engine/state.rs @@ -13,9 +13,7 @@ use crate::{ event_engine::{ types::SubscriptionInput, SubscribeEffectInvocation::{ - self, CancelHandshake, CancelHandshakeReconnect, CancelReceive, - CancelReceiveReconnect, EmitMessages, EmitStatus, Handshake, HandshakeReconnect, - Receive, ReceiveReconnect, + self, CancelHandshake, CancelReceive, EmitMessages, EmitStatus, Handshake, Receive, }, SubscribeEvent, }, @@ -52,31 +50,6 @@ pub(crate) enum SubscribeState { cursor: Option, }, - /// Subscription recover state. - /// - /// The system is recovering after the initial subscription attempt failed. - HandshakeReconnecting { - /// User input with channels and groups. - /// - /// Object contains list of channels and groups which has been used - /// during recently failed initial subscription. - input: SubscriptionInput, - - /// Custom time cursor. - /// - /// Custom cursor used by subscription loop to identify point in time - /// after which updates will be delivered. - cursor: Option, - - /// Current initial subscribe retry attempt. - /// - /// Used to track overall number of initial subscription retry attempts. - attempts: u8, - - /// Initial subscribe attempt failure reason. - reason: PubNubError, - }, - /// Initial subscription stopped state. HandshakeStopped { /// User input with channels and groups. @@ -133,31 +106,6 @@ pub(crate) enum SubscribeState { cursor: SubscriptionCursor, }, - /// Subscription recover state. - /// - /// The system is recovering after the updates receiving attempt failed. - ReceiveReconnecting { - /// User input with channels and groups. - /// - /// Object contains list of channels and groups which has been used - /// during recently failed receive updates. - input: SubscriptionInput, - - /// Time cursor. - /// - /// Cursor used by subscription loop to identify point in time after - /// which updates will be delivered. - cursor: SubscriptionCursor, - - /// Current receive retry attempt. - /// - /// Used to track overall number of receive updates retry attempts. - attempts: u8, - - /// Receive updates attempt failure reason. - reason: PubNubError, - }, - /// Updates receiving stopped state. ReceiveStopped { /// User input with channels and groups. @@ -209,34 +157,32 @@ impl SubscribeState { }), None, )), - Self::Handshaking { cursor, .. } - | Self::HandshakeReconnecting { cursor, .. } - | Self::HandshakeFailed { cursor, .. } => Some(self.transition_to( - Some(Self::Handshaking { + Self::Handshaking { cursor, .. } | Self::HandshakeFailed { cursor, .. } => { + Some(self.transition_to( + Some(Self::Handshaking { + input: SubscriptionInput::new(channels, channel_groups), + cursor: cursor.clone(), + }), + None, + )) + } + Self::HandshakeStopped { cursor, .. } => Some(self.transition_to( + Some(Self::HandshakeStopped { input: SubscriptionInput::new(channels, channel_groups), cursor: cursor.clone(), }), None, )), - Self::HandshakeStopped { cursor, .. } => Some(self.transition_to( - Some(Self::HandshakeStopped { + Self::Receiving { cursor, .. } => Some(self.transition_to( + Some(Self::Receiving { input: SubscriptionInput::new(channels, channel_groups), cursor: cursor.clone(), }), - None, + Some(vec![EmitStatus(ConnectionStatus::SubscriptionChanged { + channels: channels.clone(), + channel_groups: channel_groups.clone(), + })]), )), - Self::Receiving { cursor, .. } | Self::ReceiveReconnecting { cursor, .. } => { - Some(self.transition_to( - Some(Self::Receiving { - input: SubscriptionInput::new(channels, channel_groups), - cursor: cursor.clone(), - }), - Some(vec![EmitStatus(ConnectionStatus::SubscriptionChanged { - channels: channels.clone(), - channel_groups: channel_groups.clone(), - })]), - )) - } Self::ReceiveFailed { cursor, .. } => Some(self.transition_to( Some(Self::Handshaking { input: SubscriptionInput::new(channels, channel_groups), @@ -272,9 +218,7 @@ impl SubscribeState { }), None, )), - Self::Handshaking { .. } - | Self::HandshakeReconnecting { .. } - | Self::HandshakeFailed { .. } => Some(self.transition_to( + Self::Handshaking { .. } | Self::HandshakeFailed { .. } => Some(self.transition_to( Some(Self::Handshaking { input: SubscriptionInput::new(channels, channel_groups), cursor: Some(restore_cursor.clone()), @@ -288,7 +232,7 @@ impl SubscribeState { }), None, )), - Self::Receiving { .. } | Self::ReceiveReconnecting { .. } => Some(self.transition_to( + Self::Receiving { .. } => Some(self.transition_to( Some(Self::Receiving { input: SubscriptionInput::new(channels, channel_groups), cursor: restore_cursor.clone(), @@ -324,8 +268,7 @@ impl SubscribeState { next_cursor: &SubscriptionCursor, ) -> Option> { match self { - Self::Handshaking { input, cursor } - | Self::HandshakeReconnecting { input, cursor, .. } => { + Self::Handshaking { input, cursor } => { // Merge stored cursor with service-provided. let mut next_cursor = next_cursor.clone(); if let Some(cursor) = cursor { @@ -357,61 +300,6 @@ impl SubscribeState { match self { Self::Handshaking { input, cursor } => Some(self.transition_to( - Some(Self::HandshakeReconnecting { - input: input.clone(), - cursor: cursor.clone(), - attempts: 1, - reason: reason.clone(), - }), - None, - )), - _ => None, - } - } - - /// Handle handshake reconnect failure event. - /// - /// Event is sent if handshake reconnect effect failed due to any network - /// issues. - fn handshake_reconnect_failure_transition( - &self, - reason: &PubNubError, - ) -> Option> { - // Request cancellation shouldn't cause any transition because there - // will be another event after this. - if matches!(reason, PubNubError::RequestCancel { .. }) { - return None; - } - - match self { - Self::HandshakeReconnecting { - input, - cursor, - attempts, - .. - } => Some(self.transition_to( - Some(Self::HandshakeReconnecting { - input: input.clone(), - cursor: cursor.clone(), - attempts: attempts + 1, - reason: reason.clone(), - }), - None, - )), - _ => None, - } - } - - /// Handle handshake reconnection limit event. - /// - /// Event is sent if handshake reconnect reached maximum number of reconnect - /// attempts. - fn handshake_reconnect_give_up_transition( - &self, - reason: &PubNubError, - ) -> Option> { - match self { - Self::HandshakeReconnecting { input, cursor, .. } => Some(self.transition_to( Some(Self::HandshakeFailed { input: input.clone(), cursor: cursor.clone(), @@ -435,49 +323,19 @@ impl SubscribeState { messages: &[Update], ) -> Option> { match self { - Self::Receiving { input, .. } | Self::ReceiveReconnecting { input, .. } => { - Some(self.transition_to( - Some(Self::Receiving { - input: input.clone(), - cursor: cursor.clone(), - }), - Some(vec![EmitMessages(messages.to_vec(), cursor.clone())]), - )) - } - _ => None, - } - } - - /// Handle updates receive failure event. - fn receive_failure_transition( - &self, - reason: &PubNubError, - ) -> Option> { - // Request cancellation shouldn't cause any transition because there - // will be another event after this. - if matches!(reason, PubNubError::RequestCancel { .. }) { - return None; - } - - match self { - Self::Receiving { input, cursor, .. } => Some(self.transition_to( - Some(Self::ReceiveReconnecting { + Self::Receiving { input, .. } => Some(self.transition_to( + Some(Self::Receiving { input: input.clone(), cursor: cursor.clone(), - attempts: 1, - reason: reason.clone(), }), - None, + Some(vec![EmitMessages(messages.to_vec(), cursor.clone())]), )), _ => None, } } /// Handle updates receive failure event. - /// - /// Event is sent if updates receive effect failed due to any network - /// issues. - fn receive_reconnect_failure_transition( + fn receive_failure_transition( &self, reason: &PubNubError, ) -> Option> { @@ -488,34 +346,7 @@ impl SubscribeState { } match self { - Self::ReceiveReconnecting { - input, - attempts, - cursor, - .. - } => Some(self.transition_to( - Some(Self::ReceiveReconnecting { - input: input.clone(), - cursor: cursor.clone(), - attempts: attempts + 1, - reason: reason.clone(), - }), - None, - )), - _ => None, - } - } - - /// Handle receive updates reconnection limit event. - /// - /// Event is sent if receive updates reconnect reached maximum number of - /// reconnect attempts. - fn receive_reconnect_give_up_transition( - &self, - reason: &PubNubError, - ) -> Option> { - match self { - Self::ReceiveReconnecting { input, cursor, .. } => Some(self.transition_to( + Self::Receiving { input, cursor, .. } => Some(self.transition_to( Some(Self::ReceiveFailed { input: input.clone(), cursor: cursor.clone(), @@ -535,23 +366,20 @@ impl SubscribeState { /// channels / groups or temporally stop any activity. fn disconnect_transition(&self) -> Option> { match self { - Self::Handshaking { input, cursor } - | Self::HandshakeReconnecting { input, cursor, .. } => Some(self.transition_to( + Self::Handshaking { input, cursor } => Some(self.transition_to( Some(Self::HandshakeStopped { input: input.clone(), cursor: cursor.clone(), }), None, )), - Self::Receiving { input, cursor } | Self::ReceiveReconnecting { input, cursor, .. } => { - Some(self.transition_to( - Some(Self::ReceiveStopped { - input: input.clone(), - cursor: cursor.clone(), - }), - Some(vec![EmitStatus(ConnectionStatus::Disconnected)]), - )) - } + Self::Receiving { input, cursor } => Some(self.transition_to( + Some(Self::ReceiveStopped { + input: input.clone(), + cursor: cursor.clone(), + }), + Some(vec![EmitStatus(ConnectionStatus::Disconnected)]), + )), _ => None, } } @@ -615,32 +443,10 @@ impl State for SubscribeState { input: input.clone(), cursor: cursor.clone(), }]), - Self::HandshakeReconnecting { - input, - cursor, - attempts, - reason, - } => Some(vec![HandshakeReconnect { - input: input.clone(), - cursor: cursor.clone(), - attempts: *attempts, - reason: reason.clone(), - }]), Self::Receiving { input, cursor } => Some(vec![Receive { input: input.clone(), cursor: cursor.clone(), }]), - Self::ReceiveReconnecting { - input, - cursor, - attempts, - reason, - } => Some(vec![ReceiveReconnect { - input: input.clone(), - cursor: cursor.clone(), - attempts: *attempts, - reason: reason.clone(), - }]), _ => None, } } @@ -648,9 +454,7 @@ impl State for SubscribeState { fn exit(&self) -> Option> { match self { Self::Handshaking { .. } => Some(vec![CancelHandshake]), - Self::HandshakeReconnecting { .. } => Some(vec![CancelHandshakeReconnect]), Self::Receiving { .. } => Some(vec![CancelReceive]), - Self::ReceiveReconnecting { .. } => Some(vec![CancelReceiveReconnect]), _ => None, } } @@ -666,30 +470,16 @@ impl State for SubscribeState { channel_groups, cursor, } => self.subscription_restored_transition(channels, channel_groups, cursor), - SubscribeEvent::HandshakeSuccess { cursor } - | SubscribeEvent::HandshakeReconnectSuccess { cursor } => { + SubscribeEvent::HandshakeSuccess { cursor } => { self.handshake_success_transition(cursor) } SubscribeEvent::HandshakeFailure { reason } => { self.handshake_failure_transition(reason) } - SubscribeEvent::HandshakeReconnectFailure { reason, .. } => { - self.handshake_reconnect_failure_transition(reason) - } - SubscribeEvent::HandshakeReconnectGiveUp { reason } => { - self.handshake_reconnect_give_up_transition(reason) - } - SubscribeEvent::ReceiveSuccess { cursor, messages } - | SubscribeEvent::ReceiveReconnectSuccess { cursor, messages } => { + SubscribeEvent::ReceiveSuccess { cursor, messages } => { self.receive_success_transition(cursor, messages) } SubscribeEvent::ReceiveFailure { reason } => self.receive_failure_transition(reason), - SubscribeEvent::ReceiveReconnectFailure { reason } => { - self.receive_reconnect_failure_transition(reason) - } - SubscribeEvent::ReceiveReconnectGiveUp { reason } => { - self.receive_reconnect_give_up_transition(reason) - } SubscribeEvent::Disconnect => self.disconnect_transition(), SubscribeEvent::Reconnect { cursor } => self.reconnect_transition(cursor), SubscribeEvent::UnsubscribeAll => self.unsubscribe_all_transition(), @@ -727,7 +517,7 @@ mod should { use super::*; use crate::{ - core::{event_engine::EventEngine, RequestRetryConfiguration}, + core::event_engine::EventEngine, dx::subscribe::{ event_engine::{ effects::{ @@ -767,13 +557,7 @@ mod should { let (tx, _) = async_channel::bounded(1); EventEngine::new( - SubscribeEffectHandler::new( - call, - emit_status, - emit_message, - RequestRetryConfiguration::None, - tx, - ), + SubscribeEffectHandler::new(call, emit_status, emit_message, tx), start_state, RuntimeTokio, ) @@ -886,13 +670,12 @@ mod should { SubscribeEvent::HandshakeFailure { reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: None, - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }; "to handshake reconnect on handshake failure" @@ -908,13 +691,12 @@ mod should { SubscribeEvent::HandshakeFailure { reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }; "to handshake reconnect with custom cursor on handshake failure" @@ -1047,7 +829,7 @@ mod should { ), cursor: None, }, - SubscribeEvent::HandshakeReconnectGiveUp { + SubscribeEvent::ReceiveFailure { reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, } }, SubscribeState::Handshaking { @@ -1074,61 +856,12 @@ mod should { } #[test_case( - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::HandshakeReconnectFailure { - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }, - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - attempts: 2, - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }; - "to handshake reconnecting on reconnect failure" - )] - #[test_case( - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::HandshakeReconnectFailure { - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }, - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 2, - reason: PubNubError::Transport { details: "Test reason on error".to_string(), response: None, }, - }; - "to handshake reconnecting with custom cursor on reconnect failure" - )] - #[test_case( - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: None, - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, SubscribeEvent::SubscriptionChanged { @@ -1142,16 +875,15 @@ mod should { ), cursor: None, }; - "to handshaking on subscription change" + "to handshaking on subscription changed" )] #[test_case( - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, SubscribeEvent::SubscriptionChanged { @@ -1165,364 +897,103 @@ mod should { ), cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), }; - "to handshaking with custom cursor on subscription change" + "to handshaking with custom cursor on subscription changed" )] #[test_case( - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: None, - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, - SubscribeEvent::Disconnect, - SubscribeState::HandshakeStopped { + SubscribeEvent::Reconnect { cursor: None }, + SubscribeState::Handshaking { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: None, }; - "to handshake stopped on disconnect" + "to handshaking on reconnect" )] #[test_case( - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 1, + cursor: None, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, - SubscribeEvent::Disconnect, - SubscribeState::HandshakeStopped { + SubscribeEvent::Reconnect { + cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }) + }, + SubscribeState::Handshaking { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), }; - "to handshake stopped with custom cursor on disconnect" + "to handshaking on reconnect with custom cursor" )] #[test_case( - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), - cursor: None, - attempts: 1, + cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, - SubscribeEvent::HandshakeReconnectGiveUp { - reason: PubNubError::Transport { details: "Test give up reason".to_string(), response: None, } - }, - SubscribeState::HandshakeFailed { + SubscribeEvent::Reconnect { cursor: None }, + SubscribeState::Handshaking { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), - cursor: None, - reason: PubNubError::Transport { details: "Test give up reason".to_string(), response: None, } + cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), }; - "to handshake failed on give up" + "to handshaking with custom cursor on reconnect" )] #[test_case( - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, - SubscribeEvent::HandshakeReconnectGiveUp { - reason: PubNubError::Transport { details: "Test give up reason".to_string(), response: None, } + SubscribeEvent::Reconnect { + cursor: Some(SubscriptionCursor { timetoken: "10".into(), region: 2 }) }, - SubscribeState::HandshakeFailed { + SubscribeState::Handshaking { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - reason: PubNubError::Transport { details: "Test give up reason".to_string(), response: None, } + cursor: Some(SubscriptionCursor { timetoken: "10".into(), region: 2 }), }; - "to handshake failed with custom cursor on give up" + "to handshaking with custom cursor on reconnect with custom cursor" )] #[test_case( - SubscribeState::HandshakeReconnecting { + SubscribeState::HandshakeFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: None, - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, }, - SubscribeEvent::HandshakeReconnectSuccess { + SubscribeEvent::SubscriptionRestored { + channels: Some(vec!["ch2".to_string()]), + channel_groups: Some(vec!["gr2".to_string()]), cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 } }, - SubscribeState::Receiving { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 } - }; - "to receiving on reconnect success" - )] - #[test_case( - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::HandshakeReconnectSuccess { - cursor: SubscriptionCursor { timetoken: "10".into(), region: 2 } - }, - SubscribeState::Receiving { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "20".into(), region: 2 } - }; - "to receiving with custom cursor on reconnect success" - )] - #[test_case( - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::SubscriptionRestored { - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 } - }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch2".to_string()]), - &Some(vec!["gr2".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "10".into(), region: 1 }) - }; - "to handshaking on subscription restored" - )] - #[test_case( - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::SubscriptionRestored { - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 2 } - }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch2".to_string()]), - &Some(vec!["gr2".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "10".into(), region: 2 }), - }; - "to handshaking with custom cursor on subscription restored" - )] - #[test_case( - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::ReceiveSuccess { - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - messages: vec![] - }, - SubscribeState::HandshakeReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - attempts: 1, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }; - "to not change on unexpected event" - )] - #[tokio::test] - async fn transition_handshake_reconnecting_state( - init_state: SubscribeState, - event: SubscribeEvent, - target_state: SubscribeState, - ) { - let engine = event_engine(init_state.clone()); - assert_eq!(engine.current_state(), init_state); - - engine.process(&event); - - assert_eq!(engine.current_state(), target_state); - } - - #[test_case( - SubscribeState::HandshakeFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::SubscriptionChanged { - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch2".to_string()]), - &Some(vec!["gr2".to_string()]) - ), - cursor: None, - }; - "to handshaking on subscription changed" - )] - #[test_case( - SubscribeState::HandshakeFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::SubscriptionChanged { - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch2".to_string()]), - &Some(vec!["gr2".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - }; - "to handshaking with custom cursor on subscription changed" - )] - #[test_case( - SubscribeState::HandshakeFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::Reconnect { cursor: None }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - }; - "to handshaking on reconnect" - )] - #[test_case( - SubscribeState::HandshakeFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::Reconnect { - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }) - }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - }; - "to handshaking on reconnect with custom cursor" - )] - #[test_case( - SubscribeState::HandshakeFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::Reconnect { cursor: None }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - }; - "to handshaking with custom cursor on reconnect" - )] - #[test_case( - SubscribeState::HandshakeFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "20".into(), region: 1 }), - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::Reconnect { - cursor: Some(SubscriptionCursor { timetoken: "10".into(), region: 2 }) - }, - SubscribeState::Handshaking { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: Some(SubscriptionCursor { timetoken: "10".into(), region: 2 }), - }; - "to handshaking with custom cursor on reconnect with custom cursor" - )] - #[test_case( - SubscribeState::HandshakeFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: None, - reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, }, - }, - SubscribeEvent::SubscriptionRestored { - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 } - }, - SubscribeState::Handshaking { + SubscribeState::Handshaking { input: SubscriptionInput::new( &Some(vec!["ch2".to_string()]), &Some(vec!["gr2".to_string()]) @@ -1888,13 +1359,12 @@ mod should { SubscribeEvent::ReceiveFailure { reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, } }, - SubscribeState::ReceiveReconnecting { + SubscribeState::ReceiveFailed { input: SubscriptionInput::new( &Some(vec!["ch1".to_string()]), &Some(vec!["gr1".to_string()]) ), cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, reason: PubNubError::Transport { details: "Test reason".to_string(), response: None, } }; "to receive reconnecting on receive failure" @@ -1963,195 +1433,6 @@ mod should { assert_eq!(engine.current_state(), target_state); } - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::ReceiveReconnectFailure { - reason: PubNubError::Transport { details: "Test reconnect error".to_string(), response: None, } - }, - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 2, - reason: PubNubError::Transport { details: "Test reconnect error".to_string(), response: None, } - }; - "to receive reconnecting on reconnect failure" - )] - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::ReceiveReconnectSuccess { - cursor: SubscriptionCursor { timetoken: "100".into(), region: 1 }, - messages: vec![] - }, - SubscribeState::Receiving { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "100".into(), region: 1 }, - }; - "to receiving on reconnect success" - )] - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::SubscriptionChanged { - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - }, - SubscribeState::Receiving { - input: SubscriptionInput::new( - &Some(vec!["ch2".to_string()]), - &Some(vec!["gr2".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - }; - "to receiving on subscription changed" - )] - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::SubscriptionRestored { - channels: Some(vec!["ch2".to_string()]), - channel_groups: Some(vec!["gr2".to_string()]), - cursor: SubscriptionCursor { timetoken: "100".into(), region: 1 }, - }, - SubscribeState::Receiving { - input: SubscriptionInput::new( - &Some(vec!["ch2".to_string()]), - &Some(vec!["gr2".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "100".into(), region: 1 }, - }; - "to receiving on subscription restored" - )] - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::Disconnect, - SubscribeState::ReceiveStopped { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - }; - "to receive stopped on disconnect" - )] - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::ReceiveReconnectGiveUp { - reason: PubNubError::Transport { details: "Test give up error".to_string(), response: None, } - }, - SubscribeState::ReceiveFailed { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - reason: PubNubError::Transport { details: "Test give up error".to_string(), response: None, } - }; - "to receive failed on give up" - )] - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::UnsubscribeAll, - SubscribeState::Unsubscribed; - "to unsubscribed on unsubscribe all" - )] - #[test_case( - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }, - SubscribeEvent::HandshakeSuccess { - cursor: SubscriptionCursor { timetoken: "100".into(), region: 1 }, - }, - SubscribeState::ReceiveReconnecting { - input: SubscriptionInput::new( - &Some(vec!["ch1".to_string()]), - &Some(vec!["gr1".to_string()]) - ), - cursor: SubscriptionCursor { timetoken: "10".into(), region: 1 }, - attempts: 1, - reason: PubNubError::Transport { details: "Test error".to_string(), response: None, } - }; - "to not change on unexpected event" - )] - #[tokio::test] - async fn transition_receiving_reconnecting_state( - init_state: SubscribeState, - event: SubscribeEvent, - target_state: SubscribeState, - ) { - let engine = event_engine(init_state.clone()); - assert_eq!(engine.current_state(), init_state); - - engine.process(&event); - - assert_eq!(engine.current_state(), target_state); - } - #[test_case( SubscribeState::ReceiveFailed { input: SubscriptionInput::new( diff --git a/src/dx/subscribe/event_engine/types.rs b/src/dx/subscribe/event_engine/types.rs index 758d9b00..ea9417c3 100644 --- a/src/dx/subscribe/event_engine/types.rs +++ b/src/dx/subscribe/event_engine/types.rs @@ -7,7 +7,6 @@ //! [`PubNub`]:https://www.pubnub.com/ use crate::{ - core::PubNubError, lib::{ alloc::collections::HashSet, core::{ @@ -63,8 +62,8 @@ impl SubscriptionInput { }) }); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); Self { channels, @@ -94,7 +93,7 @@ impl SubscriptionInput { pub fn contains_channel(&self, channel: &str) -> bool { self.channels .as_ref() - .map_or(false, |channels| channels.contains(channel)) + .is_some_and(|channels| channels.contains(channel)) } pub fn channel_groups(&self) -> Option> { @@ -106,9 +105,7 @@ impl SubscriptionInput { pub fn contains_channel_group(&self, channel_group: &str) -> bool { self.channel_groups .as_ref() - .map_or(false, |channel_groups| { - channel_groups.contains(channel_group) - }) + .is_some_and(|channel_groups| channel_groups.contains(channel_group)) } fn join_sets( @@ -143,8 +140,8 @@ impl Add for SubscriptionInput { fn add(self, rhs: Self) -> Self::Output { let channel_groups = self.join_sets(&self.channel_groups, &rhs.channel_groups); let channels = self.join_sets(&self.channels, &rhs.channels); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); Self { channels, @@ -164,8 +161,8 @@ impl AddAssign for SubscriptionInput { fn add_assign(&mut self, rhs: Self) { let channel_groups = self.join_sets(&self.channel_groups, &rhs.channel_groups); let channels = self.join_sets(&self.channels, &rhs.channels); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); self.channels = channels; self.channel_groups = channel_groups; @@ -179,8 +176,8 @@ impl Sub for SubscriptionInput { fn sub(self, rhs: Self) -> Self::Output { let channel_groups = self.sub_sets(&self.channel_groups, &rhs.channel_groups); let channels = self.sub_sets(&self.channels, &rhs.channels); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); Self { channels, @@ -194,8 +191,8 @@ impl SubAssign for SubscriptionInput { fn sub_assign(&mut self, rhs: Self) { let channel_groups = self.sub_sets(&self.channel_groups, &rhs.channel_groups); let channels = self.sub_sets(&self.channels, &rhs.channels); - let channel_groups_is_empty = channel_groups.as_ref().map_or(true, |set| set.is_empty()); - let channels_is_empty = channels.as_ref().map_or(true, |set| set.is_empty()); + let channel_groups_is_empty = channel_groups.as_ref().is_none_or(|set| set.is_empty()); + let channels_is_empty = channels.as_ref().is_none_or(|set| set.is_empty()); self.channels = channels; self.channel_groups = channel_groups; @@ -225,12 +222,6 @@ pub(crate) struct SubscriptionParams<'execution> { /// Time cursor. pub cursor: Option<&'execution SubscriptionCursor>, - /// How many consequent retry attempts has been made. - pub attempt: u8, - - /// Reason why previous request created by subscription event engine failed. - pub reason: Option, - /// Effect identifier. /// /// Identifier of effect which requested to create request. diff --git a/src/dx/subscribe/mod.rs b/src/dx/subscribe/mod.rs index 27c4c8e0..aacdee8e 100644 --- a/src/dx/subscribe/mod.rs +++ b/src/dx/subscribe/mod.rs @@ -1,12 +1,9 @@ //! Subscribe module. //! -//! Allows subscribe to real-time updates from channels and groups. +//! Allows to subscribe to real-time updates from channels and groups. #[cfg(feature = "std")] -use futures::{ - future::{ready, BoxFuture}, - FutureExt, -}; +use futures::{future::BoxFuture, FutureExt}; #[cfg(feature = "std")] use spin::RwLock; @@ -18,13 +15,10 @@ use crate::{ }; #[cfg(feature = "std")] -use crate::{ - core::{ - event_engine::{CancellationTask, EventEngine}, - runtime::Runtime, - DataStream, PubNubEntity, - }, - lib::alloc::string::ToString, +use crate::core::{ + event_engine::{CancellationTask, EventEngine}, + runtime::Runtime, + DataStream, PubNubEntity, }; use crate::{ @@ -473,35 +467,15 @@ where let emit_messages_client = self.clone(); let emit_status_client = self.clone(); let subscribe_client = self.clone(); - let request_retry = self.config.transport.retry_configuration.clone(); - let request_subscribe_retry = request_retry.clone(); let runtime = self.runtime.clone(); - let runtime_sleep = runtime.clone(); let (cancel_tx, cancel_rx) = async_channel::bounded::(channel_bound); EventEngine::new( SubscribeEffectHandler::new( Arc::new(move |params| { - let delay_in_microseconds = request_subscribe_retry.retry_delay( - Some("/v2/subscribe".to_string()), - ¶ms.attempt, - params.reason.as_ref(), - ); - let inner_runtime_sleep = runtime_sleep.clone(); - Self::subscribe_call( subscribe_client.clone(), params.clone(), - Arc::new(move || { - if let Some(delay) = delay_in_microseconds { - inner_runtime_sleep - .clone() - .sleep_microseconds(delay) - .boxed() - } else { - ready(()).boxed() - } - }), cancel_rx.clone(), ) }), @@ -509,7 +483,6 @@ where Arc::new(Box::new(move |updates, cursor: SubscriptionCursor| { Self::emit_messages(emit_messages_client.clone(), updates, cursor) })), - request_retry, cancel_tx, ), SubscribeState::Unsubscribed, @@ -517,15 +490,11 @@ where ) } - fn subscribe_call( + fn subscribe_call( client: Self, params: event_engine::types::SubscriptionParams, - delay: Arc, cancel_rx: async_channel::Receiver, - ) -> BoxFuture<'static, Result> - where - F: Fn() -> BoxFuture<'static, ()> + Send + Sync + 'static, - { + ) -> BoxFuture<'static, Result> { let mut request = client .subscribe_request() .cursor(params.cursor.cloned().unwrap_or_default()); // TODO: is this clone required? @@ -548,9 +517,7 @@ where let cancel_task = CancellationTask::new(cancel_rx, params.effect_id.to_owned()); // TODO: needs to be owned? - request - .execute_with_cancel_and_delay(delay, cancel_task) - .boxed() + request.execute_with_cancel(cancel_task).boxed() } /// Subscription event engine presence `join` announcement. @@ -634,7 +601,8 @@ impl PubNubClientInstance { /// Listeners configure [`PubNubClient`] to receive real-time updates for /// specified list of channels and groups. /// - /// ```no_run // Starts listening for real-time updates + /// ```no_run + /// // Starts listening for real-time updates /// use futures::StreamExt; /// use pubnub::{ /// subscribe::{ @@ -771,6 +739,46 @@ mod should { pub display_name: String, } + /// Requests handler function type. + type RequestHandler = Box; + + #[derive(Default)] + struct MockTransportWithHandler { + /// Response which mocked transport should return. + response: Option, + + /// Request handler function which will be called before returning + /// response. + /// + /// Use function to verify request parameters. + request_handler: Option, + } + + #[async_trait::async_trait] + impl Transport for MockTransportWithHandler { + async fn send(&self, req: TransportRequest) -> Result { + // Calling request handler (if provided). + if let Some(handler) = &self.request_handler { + handler(&req); + } + + Ok(self.response.clone().unwrap_or(transport_response(200))) + } + } + + /// Service response payload. + fn transport_response(status: u16) -> TransportResponse { + TransportResponse { + status, + body: Some(Vec::from(if status < 400 { + "{\"t\":{\"t\":\"17613449864766754\",\"r\":21},\"m\":[]}" + } else { + "\"error\":{{\"message\":\"Overall error\",\"source\":\"test\",\"details\":[{{\"message\":\"Error\",\"location\":\"signature\",\"locationType\":\"query\"}}]}}" + })), + ..Default::default() + } + } + struct MockTransport { responses_count: RwLock, } @@ -886,8 +894,11 @@ mod should { } } - fn client() -> PubNubGenericClient { - PubNubClientBuilder::with_transport(Default::default()) + fn client(transport: Option) -> PubNubGenericClient + where + T: Transport + Default, + { + PubNubClientBuilder::with_transport(transport.unwrap_or_default()) .with_keyset(Keyset { subscribe_key: "demo", publish_key: Some("demo"), @@ -900,7 +911,7 @@ mod should { #[tokio::test] async fn create_subscription_set() { - let _ = client().subscription(SubscriptionParams { + let _ = client::(None).subscription(SubscriptionParams { channels: Some(&["channel_a"]), channel_groups: Some(&["group_a"]), options: None, @@ -909,7 +920,7 @@ mod should { #[tokio::test] async fn subscribe() { - let client = client(); + let client = client::(None); let subscription = client.subscription(SubscriptionParams { channels: Some(&["my-channel"]), channel_groups: Some(&["group_a"]), @@ -943,9 +954,33 @@ mod should { client.unsubscribe_all(); } + #[tokio::test] + async fn subscribe_with_unique_channels_and_groups() { + let transport = MockTransportWithHandler { + response: None, + request_handler: Some(Box::new(|req| { + if req.path.starts_with("/v2/subscribe") { + assert_eq!(req.path.split('/').collect::>()[4], "channel_a"); + assert_eq!(req.query_parameters["channel-group"], "group_b"); + } + })), + }; + let client = client(Some(transport)); + let subscription = client.subscription(SubscriptionParams { + channels: Some(&["channel_a", "channel_a", "channel_a"]), + channel_groups: Some(&["group_b", "group_b", "group_b"]), + options: None, + }); + subscription.subscribe(); + + let status = client.status_stream().next().await.unwrap(); + + assert!(matches!(status, ConnectionStatus::Connected)); + } + #[tokio::test] async fn subscribe_raw() { - let subscription = client() + let subscription = client::(None) .subscribe_raw() .channels(["world".into()].to_vec()) .execute() @@ -959,7 +994,7 @@ mod should { #[test] fn subscribe_raw_blocking() { - let subscription = client() + let subscription = client::(None) .subscribe_raw() .channels(["world".into()].to_vec()) .execute_blocking() diff --git a/src/dx/subscribe/result.rs b/src/dx/subscribe/result.rs index a393d901..79ca22d0 100644 --- a/src/dx/subscribe/result.rs +++ b/src/dx/subscribe/result.rs @@ -209,7 +209,7 @@ pub struct Envelope { /// PubNub defined event type. #[cfg_attr( feature = "serde", - serde(rename = "f"), + serde(rename = "e"), serde(default = "Envelope::default_message_type") )] pub message_type: SubscribeMessageType, @@ -260,7 +260,7 @@ pub struct Envelope { /// `r#type`). /// /// [`publish`]: crate::dx::publish - #[cfg_attr(feature = "serde", serde(rename = "mt"), serde(default))] + #[cfg_attr(feature = "serde", serde(rename = "cmt"), serde(default))] pub r#type: Option, /// Identifier of space into which message has been published (set only when @@ -269,6 +269,22 @@ pub struct Envelope { /// [`publish`]: crate::dx::publish #[cfg_attr(feature = "serde", serde(rename = "si"), serde(default))] pub space_id: Option, + + #[cfg(feature = "serde")] + /// User provided metadata (set only when [`publish`] called with + /// `meta`). + /// + /// [`publish`]: crate::dx::publish + #[cfg_attr(feature = "serde", serde(rename = "u"))] + pub user_metadata: Option, + + #[cfg(not(feature = "serde"))] + /// User provided metadata (set only when [`publish`] called with + /// `meta`). + /// + /// [`publish`]: crate::dx::publish + #[cfg_attr(feature = "serde", serde(rename = "u"))] + pub user_metadata: Option>, } /// Payload of the real-time update. @@ -278,28 +294,48 @@ pub struct Envelope { #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))] pub enum EnvelopePayload { - /// Presence change real-time update. + /// Presence state change real-time update. + PresenceStateChange { + /// Presence event type. + action: String, + + /// Unix timestamp when presence event has been triggered. + timestamp: usize, + + /// The current occupancy after the presence change is updated. + occupancy: usize, + + /// Unique identification of the user for whom the presence event has + /// been triggered. + uuid: String, + + /// The user's state associated with the channel has been updated. + #[cfg(feature = "serde")] + data: serde_json::Value, + + /// The user's state associated with the channel has been updated. + #[cfg(not(feature = "serde"))] + data: Vec, + }, + /// Presence change announce real-time update. /// /// Payload represents one of the presence types: /// * `join` – new user joined the channel /// * `leave` – some user left channel /// * `timeout` – service didn't notice user for a while - /// * `interval` – bulk update on `joined`, `left` and `timeout` users. - /// * `state-change` - some user changed state associated with him on - /// channel. - Presence { + PresenceAnnounce { /// Presence event type. - action: Option, + action: String, /// Unix timestamp when presence event has been triggered. timestamp: usize, /// Unique identification of the user for whom the presence event has /// been triggered. - uuid: Option, + uuid: String, /// The current occupancy after the presence change is updated. - occupancy: Option, + occupancy: usize, /// The user's state associated with the channel has been updated. #[cfg(feature = "serde")] @@ -308,6 +344,14 @@ pub enum EnvelopePayload { /// The user's state associated with the channel has been updated. #[cfg(not(feature = "serde"))] data: Option>, + }, + /// Presence bulk update on `joined`, `left` and `timeout` users. + PresenceInterval { + /// Unix timestamp when presence event has been triggered. + timestamp: usize, + + /// The current occupancy after the presence change is updated. + occupancy: usize, /// The list of unique user identifiers that `joined` the channel since /// the last interval presence update. @@ -320,6 +364,14 @@ pub enum EnvelopePayload { /// The list of unique user identifiers that `timeout` the channel since /// the last interval presence update. timeout: Option>, + + /// Indicates whether presence should be requested manually or not. + /// + /// Depending on from the presence activity, the resulting interval + /// update can be too large to be returned as a presence event with + /// subscribe REST API response. The server will set this flag + /// to `true` in this case. + here_now_refresh: Option, }, /// Object realtime update. Object { @@ -577,7 +629,11 @@ impl TryFrom for Update { fn try_from(value: Envelope) -> Result { match value.payload { - EnvelopePayload::Presence { .. } => Ok(Update::Presence(value.try_into()?)), + EnvelopePayload::PresenceAnnounce { .. } + | EnvelopePayload::PresenceInterval { .. } + | EnvelopePayload::PresenceStateChange { .. } => { + Ok(Update::Presence(value.try_into()?)) + } EnvelopePayload::Object { .. } if matches!(value.message_type, SubscribeMessageType::Object) => { diff --git a/src/dx/subscribe/subscription_manager.rs b/src/dx/subscribe/subscription_manager.rs index 93cc16aa..37fa69ea 100644 --- a/src/dx/subscribe/subscription_manager.rs +++ b/src/dx/subscribe/subscription_manager.rs @@ -371,7 +371,6 @@ mod should { use super::*; use crate::{ - core::RequestRetryConfiguration, dx::subscribe::{ event_engine::{SubscribeEffectHandler, SubscribeState}, result::SubscribeResult, @@ -415,7 +414,6 @@ mod should { Arc::new(Box::new(|_, _| { // Do nothing yet })), - RequestRetryConfiguration::None, cancel_tx, ), SubscribeState::Unsubscribed, diff --git a/src/dx/subscribe/subscription_set.rs b/src/dx/subscribe/subscription_set.rs index a967aceb..5d267efe 100644 --- a/src/dx/subscribe/subscription_set.rs +++ b/src/dx/subscribe/subscription_set.rs @@ -1174,7 +1174,7 @@ mod it_should { .into_iter() .map(|name| client.channel(name).subscription(None)) .collect::>>(); - let channels_3_subscriptions = vec![ + let channels_3_subscriptions = [ channels_1_subscriptions[0].clone(), channels_2_subscriptions[1].clone(), ]; diff --git a/src/dx/subscribe/types.rs b/src/dx/subscribe/types.rs index a44a8a6a..6976bbf1 100644 --- a/src/dx/subscribe/types.rs +++ b/src/dx/subscribe/types.rs @@ -22,9 +22,6 @@ use crate::{ }, }; -#[cfg(not(feature = "serde"))] -use crate::lib::alloc::vec; - /// Subscription event. /// /// This enum provides two variants: [`SubscribeStreamEvent::Status`] and @@ -58,7 +55,7 @@ pub enum SubscribeMessageType { /// Small message. /// - /// Message sent with separate endpoint as chunk of really small data. + /// Message sent with separate endpoint as chunk of tiny data. Signal = 1, /// Object related event. @@ -290,6 +287,9 @@ pub enum Presence { /// /// Time when event has been emitted. event_timestamp: usize, + + /// Indicates whether presence should be requested manually or not. + here_now_refresh: bool, }, /// Remote user `state` change update. @@ -481,6 +481,20 @@ pub struct Message { /// [`publish`]: crate::dx::publish pub space_id: Option, + #[cfg(feature = "serde")] + /// User provided metadata (set only when [`publish`] called with + /// `meta`). + /// + /// [`publish`]: crate::dx::publish + pub user_metadata: Option, + + /// User provided metadata (set only when [`publish`] called with + /// `meta`). + /// + /// [`publish`]: crate::dx::publish + #[cfg(not(feature = "serde"))] + pub user_metadata: Option>, + /// Decryption error details. /// /// Error is set when [`PubNubClient`] configured with cryptor, and it @@ -877,78 +891,79 @@ impl TryFrom for Presence { fn try_from(value: Envelope) -> Result { let event_timestamp = value.published.timetoken.parse::().ok().unwrap_or(0); - if let EnvelopePayload::Presence { + let subscription = resolve_subscription_value(value.subscription, &value.channel); + let channel = value.channel.replace("-pnpres", ""); + + if let EnvelopePayload::PresenceStateChange { + timestamp, + uuid, + data, + .. + } = value.payload + { + Ok(Self::StateChange { + timestamp, + uuid, + channel, + subscription, + data, + event_timestamp, + }) + } else if let EnvelopePayload::PresenceAnnounce { action, timestamp, uuid, occupancy, data, - join, - leave, - timeout, } = value.payload { - let action = action.unwrap_or("interval".to_string()); - - let subscription = resolve_subscription_value(value.subscription, &value.channel); - let channel = value.channel.replace("-pnpres", ""); - match action.as_str() { "join" => Ok(Self::Join { timestamp, - // `join` event always has `uuid` and unwrap_or default - // value won't be actually used. - uuid: uuid.unwrap_or("".to_string()), + uuid, channel, subscription, - occupancy: occupancy.unwrap_or(0), + occupancy, data, event_timestamp, }), "leave" => Ok(Self::Leave { timestamp, - // `leave` event always has `uuid` and unwrap_or default - // value won't be actually used. - uuid: uuid.unwrap_or("".to_string()), - channel, - subscription, - occupancy: occupancy.unwrap_or(0), - event_timestamp, - }), - "timeout" => Ok(Self::Timeout { - timestamp, - // `leave` event always has `uuid` and unwrap_or default - // value won't be actually used. - uuid: uuid.unwrap_or("".to_string()), - channel, - subscription, - occupancy: occupancy.unwrap_or(0), - event_timestamp, - }), - "interval" => Ok(Self::Interval { - timestamp, + uuid, channel, subscription, - occupancy: occupancy.unwrap_or(0), - join, - leave, - timeout, + occupancy, event_timestamp, }), - _ => Ok(Self::StateChange { + _ => Ok(Self::Timeout { timestamp, - // `state-change` event always has `uuid` and unwrap_or - // default value won't be actually used. - uuid: uuid.unwrap_or("".to_string()), + uuid, channel, subscription, - #[cfg(feature = "serde")] - data: data.unwrap_or(serde_json::Value::Null), - #[cfg(not(feature = "serde"))] - data: data.unwrap_or(vec![]), + occupancy, event_timestamp, }), } + } else if let EnvelopePayload::PresenceInterval { + timestamp, + occupancy, + join, + leave, + timeout, + here_now_refresh, + } = value.payload + { + Ok(Self::Interval { + timestamp, + channel, + subscription, + occupancy, + join, + leave, + timeout, + here_now_refresh: here_now_refresh.unwrap_or(false), + event_timestamp, + }) } else { Err(PubNubError::Deserialization { details: "Unable deserialize: unexpected payload for presence.".to_string(), @@ -1099,6 +1114,7 @@ impl TryFrom for Message { data: value.payload.into(), r#type: value.r#type, space_id: value.space_id, + user_metadata: value.user_metadata, decryption_error: None, }) } else { diff --git a/src/lib.rs b/src/lib.rs index 0b9a37f7..c67ae515 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,11 +39,11 @@ //! ```toml //! # default features //! [dependencies] -//! pubnub = "0.6.0" +//! pubnub = "0.7.0" //! //! # all features //! [dependencies] -//! pubnub = { version = "0.6.0", features = ["full"] } +//! pubnub = { version = "0.7.0", features = ["full"] } //! ``` //! //! ### Example @@ -167,11 +167,11 @@ //! ```toml //! # only blocking and access + default features //! [dependencies] -//! pubnub = { version = "0.6.0", features = ["blocking", "access"] } +//! pubnub = { version = "0.7.0", features = ["blocking", "access"] } //! //! # only parse_token + default features //! [dependencies] -//! pubnub = { version = "0.6.0", features = ["parse_token"] } +//! pubnub = { version = "0.7.0", features = ["parse_token"] } //! ``` //! //! ### Available features @@ -210,7 +210,7 @@ //! //! ```toml //! [dependencies] -//! pubnub = { version = "0.6.0", default-features = false, features = ["serde", "publish", +//! pubnub = { version = "0.7.0", default-features = false, features = ["serde", "publish", //! "blocking"] } //! ``` //! @@ -322,11 +322,11 @@ mod lib { pub(crate) use hash_map::HashMap; pub(crate) mod hash_map { - /// Depending of the `std` feature, this module will re-export - /// either `std::collections::HashMap` or `hashbrown::HashMap`. - /// This is needed because there is no `no_std` HashMap available. - /// We decided to use `hashbrown` because it is fast and has the - /// same API as `std` HashMap. + //! Depending on the `std` feature, this module will re-export + //! either `std::collections::HashMap` or `hashbrown::HashMap`. + //! This is needed because there is no `no_std` HashMap available. + //! We decided to use `hashbrown` because it is fast and has the + //! same API as `std` HashMap. #[cfg(not(feature = "std"))] pub(crate) use hashbrown::HashMap; @@ -336,16 +336,3 @@ mod lib { } } } - -// Mocking random for checking if `no_std` compiles. -// Don't use that feature in production. -#[cfg(feature = "mock_getrandom")] -mod mock_getrandom { - use getrandom::{register_custom_getrandom, Error}; - - pub fn do_nothing(_buf: &mut [u8]) -> Result<(), Error> { - Ok(()) - } - - register_custom_getrandom!(do_nothing); -} diff --git a/src/providers/crypto/cryptors/aes_cbc.rs b/src/providers/crypto/cryptors/aes_cbc.rs index aa2468b6..96cb34fd 100644 --- a/src/providers/crypto/cryptors/aes_cbc.rs +++ b/src/providers/crypto/cryptors/aes_cbc.rs @@ -49,7 +49,7 @@ impl AesCbcCryptor { fn initialization_vector(&self) -> [u8; AES_BLOCK_SIZE] { let mut random = [0u8; AES_BLOCK_SIZE]; - getrandom::getrandom(&mut random).ok(); + getrandom::fill(&mut random).ok(); random } diff --git a/src/providers/crypto/cryptors/legacy.rs b/src/providers/crypto/cryptors/legacy.rs index 725dc8fa..3ba7ccfb 100644 --- a/src/providers/crypto/cryptors/legacy.rs +++ b/src/providers/crypto/cryptors/legacy.rs @@ -64,7 +64,7 @@ impl LegacyCryptor { fn initialization_vector(&self) -> [u8; 16] { if self.use_random_iv { let mut random = [0u8; AES_BLOCK_SIZE]; - getrandom::getrandom(&mut random).ok(); + getrandom::fill(&mut random).ok(); random } else { *b"0123456789012345" diff --git a/src/transport/reqwest.rs b/src/transport/reqwest.rs index 18bb30fc..c164dfae 100644 --- a/src/transport/reqwest.rs +++ b/src/transport/reqwest.rs @@ -254,7 +254,13 @@ fn create_result( ) -> Result { Ok(TransportResponse { status: status.as_u16(), - body: (!body.is_empty()).then(|| body.to_vec()), + body: (!body.is_empty()).then(|| { + info!( + "Received payload: {}", + String::from_utf8(body.to_vec()).unwrap_or("can't decode body".to_string()) + ); + body.to_vec() + }), headers: extract_headers(headers), }) } diff --git a/tests/crypto/legacy/crypto_aescbc.rs b/tests/crypto/legacy/crypto_aescbc.rs index a537b1c9..422df9a7 100644 --- a/tests/crypto/legacy/crypto_aescbc.rs +++ b/tests/crypto/legacy/crypto_aescbc.rs @@ -145,7 +145,7 @@ impl AesCbcCrypto { Some(iv) => Vec::from(iv.as_slice()), None => { let mut random = [0u8; AES_BLOCK_SIZE]; - getrandom::getrandom(&mut random).ok(); + getrandom::fill(&mut random).ok(); Vec::from(random) } } diff --git a/tests/presence/presence_steps.rs b/tests/presence/presence_steps.rs index ad8e7404..f6f80c05 100644 --- a/tests/presence/presence_steps.rs +++ b/tests/presence/presence_steps.rs @@ -6,7 +6,6 @@ use std::fs::read_to_string; use crate::clear_log_file; use crate::common::PubNubWorld; -use pubnub::core::RequestRetryConfiguration; use pubnub::subscribe::{ EventEmitter, EventSubscriber, Presence, Subscriber, SubscriptionOptions, SubscriptionSet, }; @@ -25,19 +24,11 @@ fn events_and_invocations_history() -> Vec> { "LEFT_ALL", "HEARTBEAT_SUCCESS", "HEARTBEAT_FAILURE", - "HEARTBEAT_GIVEUP", "RECONNECT", "DISCONNECT", "TIMES_UP", ]; - let known_invocations = [ - "HEARTBEAT", - "DELAYED_HEARTBEAT", - "CANCEL_DELAYED_HEARTBEAT", - "LEAVE", - "WAIT", - "CANCEL_WAIT", - ]; + let known_invocations = ["HEARTBEAT", "LEAVE", "WAIT", "CANCEL_WAIT"]; for line in written_log.lines() { if !line.contains(" DEBUG ") { @@ -188,22 +179,13 @@ async fn wait_presence_join(world: &mut PubNubWorld) { } #[then("I receive an error in my heartbeat response")] -async fn receive_an_error_heartbeat_retry(world: &mut PubNubWorld) { +async fn receive_an_error_heartbeat_retry(_world: &mut PubNubWorld) { tokio::time::sleep(tokio::time::Duration::from_secs(4)).await; let history = events_and_invocations_history(); - let expected_retry_count: usize = usize::from(match &world.retry_policy.clone().unwrap() { - RequestRetryConfiguration::Linear { max_retry, .. } - | RequestRetryConfiguration::Exponential { max_retry, .. } => *max_retry, - _ => 0, - }); assert_eq!( event_occurrence_count(history.clone(), "HEARTBEAT_FAILURE".into()), - expected_retry_count + 1 - ); - assert_eq!( - event_occurrence_count(history, "HEARTBEAT_GIVEUP".into()), 1 ); } diff --git a/tests/subscribe/subscribe_steps.rs b/tests/subscribe/subscribe_steps.rs index b5d05aca..245ad3ad 100644 --- a/tests/subscribe/subscribe_steps.rs +++ b/tests/subscribe/subscribe_steps.rs @@ -3,7 +3,6 @@ use crate::{clear_log_file, scenario_name}; use cucumber::gherkin::Table; use cucumber::{codegen::Regex, gherkin::Step, then, when}; use futures::{select_biased, FutureExt, StreamExt}; -use pubnub::core::RequestRetryConfiguration; use pubnub::subscribe::{EventEmitter, EventSubscriber, SubscriptionCursor, SubscriptionParams}; use std::fs::read_to_string; @@ -19,14 +18,8 @@ fn events_and_invocations_history() -> Vec> { "SUBSCRIPTION_RESTORED", "HANDSHAKE_SUCCESS", "HANDSHAKE_FAILURE", - "HANDSHAKE_RECONNECT_SUCCESS", - "HANDSHAKE_RECONNECT_FAILURE", - "HANDSHAKE_RECONNECT_GIVEUP", "RECEIVE_SUCCESS", "RECEIVE_FAILURE", - "RECEIVE_RECONNECT_SUCCESS", - "RECEIVE_RECONNECT_FAILURE", - "RECEIVE_RECONNECT_GIVEUP", "DISCONNECT", "RECONNECT", "UNSUBSCRIBE_ALL", @@ -34,12 +27,8 @@ fn events_and_invocations_history() -> Vec> { let known_invocations = [ "HANDSHAKE", "CANCEL_HANDSHAKE", - "HANDSHAKE_RECONNECT", - "CANCEL_HANDSHAKE_RECONNECT", "RECEIVE_MESSAGES", "CANCEL_RECEIVE_MESSAGES", - "RECEIVE_RECONNECT", - "CANCEL_RECEIVE_RECONNECT", "EMIT_STATUS", "EMIT_MESSAGES", ]; @@ -167,11 +156,6 @@ async fn receive_an_error_subscribe_retry(world: &mut PubNubWorld) { _ = subscription.next().fuse() => panic!("Message update from server") } - let expected_retry_count: usize = usize::from(match &world.retry_policy.clone().unwrap() { - RequestRetryConfiguration::Linear { max_retry, .. } - | RequestRetryConfiguration::Exponential { max_retry, .. } => *max_retry, - _ => 0, - }); tokio::time::sleep(tokio::time::Duration::from_secs(4)).await; let handshake_test = scenario_name(world).to_lowercase().contains("handshake"); @@ -183,36 +167,12 @@ async fn receive_an_error_subscribe_retry(world: &mut PubNubWorld) { "RECEIVE_FAILURE" } }; - let reconnect_operation_name = { - if handshake_test { - "HANDSHAKE_RECONNECT_FAILURE" - } else { - "RECEIVE_RECONNECT_FAILURE" - } - }; - let give_up_operation_name = { - if handshake_test { - "HANDSHAKE_RECONNECT_GIVEUP" - } else { - "RECEIVE_RECONNECT_GIVEUP" - } - }; assert_eq!( event_occurrence_count(history.clone(), normal_operation_name.into()), 1, "{normal_operation_name} should appear at least once" ); - assert_eq!( - event_occurrence_count(history.clone(), reconnect_operation_name.into()), - expected_retry_count, - "{reconnect_operation_name} should appear {expected_retry_count} times" - ); - assert_eq!( - event_occurrence_count(history, give_up_operation_name.into()), - 1, - "{give_up_operation_name} should appear at least once" - ); } #[then("I observe the following:")]