diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 000000000..3f50e67b5 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,2 @@ +[profile.default] +retries = { backoff = "exponential", count = 3, delay = "5s" } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbf060b87..46bca5010 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: override: true + - uses: taiki-e/install-action@nextest - name: cargo fetch uses: actions-rs/cargo@v1 with: @@ -53,12 +54,12 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --verbose --release --tests + args: --verbose --tests - name: Run tests uses: actions-rs/cargo@v1 with: - command: test - args: --verbose --release + command: nextest + args: run --verbose test_coverage: name: Code coverage in tests @@ -69,16 +70,12 @@ jobs: - uses: actions-rs/toolchain@v1 with: override: true - - name: Install cargo-binstall - uses: cargo-bins/cargo-binstall@v1.12.0 - - name: Install cargo-tarpaulin - uses: actions-rs/cargo@v1 - with: - command: binstall - args: cargo-tarpaulin --no-confirm + components: llvm-tools-preview + - uses: taiki-e/install-action@cargo-llvm-cov + - uses: taiki-e/install-action@nextest - name: Generate code coverage run: | - cargo tarpaulin --avoid-cfg-tarpaulin --timeout=360 --out lcov --exclude-files 'bindings/**/*.*' --exclude-files 'ergo-rest/src/reqwest.rs' --exclude-files 'ergo-rest/src/reqwest/**/*.*' --exclude-files 'ergo-rest/src/wasm_timer.rs' --exclude-files 'ergo-rest/src/wasm_timer/**/*.*' + cargo llvm-cov --lcov --output-path lcov.info --ignore-filename-regex "bindings/.*|ergo-rest/src/reqwest.*|ergo-rest/src/wasm_timer.*" nextest - name: Push code coverage results to coveralls.io uses: coverallsapp/github-action@master with: @@ -238,7 +235,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2025-03-28 + toolchain: nightly-2025-11-10 override: true - name: install deps diff --git a/Cargo.toml b/Cargo.toml index a5856e200..507decddf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ num-bigint = { version = "0.4.0", default-features = false } lazy_static = { version = "1.4", features = ["spin_no_std"] } bs58 = { version = "0.4.0", default-features = false, features = ["alloc"] } base16 = { version = "0.2.1", default-features = false, features = ["alloc"] } -base64 = { version = "0.13.0", default-features = false, features = ["alloc"] } +base64 = { version = "0.22.1", default-features = false, features = ["alloc"] } indexmap = { version = "2.6.0", default-features = false } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false, features = [ @@ -82,15 +82,15 @@ serde_with = { version = "3.11.0", default-features = false, features = [ rand = "0.8.5" bytes = { version = "1.1", default-features = false } futures = "0.3" -tokio = { version = "1.15.0", features = ["full"] } +tokio = { version = "1.15.0", default-features = false, features = ["rt", "rt-multi-thread"] } tokio-stream = { version = "0.1.8", features = ["sync", "time"] } -tokio-util = { version = "0.6.9", features = ["codec"] } +tokio-util = { version = "0.7.17", features = ["codec"] } bounded-integer = { version = "^0.5", features = ["types"] } url = "2.5.4" getrandom = { version = "0.2.16" } itertools = { version = "0.10.3", default-features = false } -miette = { version = "5", features = ["fancy"] } -hashbrown = { version = "0.14.3", features = ["serde"] } +miette = { version = "7.6.0", features = ["fancy"] } +hashbrown = { version = "0.16.1", features = ["serde"] } core2 = { version = "0.4.0", default-features = false, features = ["alloc"] } # dev-dependencies proptest = { version = "=1.6.0", default-features = false, features = [ diff --git a/bindings/ergo-lib-python/Cargo.toml b/bindings/ergo-lib-python/Cargo.toml index e7011c161..a5688af88 100644 --- a/bindings/ergo-lib-python/Cargo.toml +++ b/bindings/ergo-lib-python/Cargo.toml @@ -12,11 +12,11 @@ name = "ergo_lib_python" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.24.2", features = ["indexmap"] } +pyo3 = { version = "0.27.1", features = ["indexmap"] } base16 = { workspace = true } derive_more = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } ergo-lib = { workspace = true, features = ["mnemonic_gen"] } sigma-ser = { workspace = true } -serde-pyobject = "0.6.1" +serde-pyobject = "0.8.0" diff --git a/bindings/ergo-lib-python/src/chain/constant.rs b/bindings/ergo-lib-python/src/chain/constant.rs index de57205f0..c5470fc82 100644 --- a/bindings/ergo-lib-python/src/chain/constant.rs +++ b/bindings/ergo-lib-python/src/chain/constant.rs @@ -108,7 +108,7 @@ impl SType { tpes.bind(py) .iter() .map(|tpe| -> PyResult { - tpe.downcast::()?.get().to_stype(py) + tpe.cast::()?.get().to_stype(py) }) .collect::>>()?, ) @@ -145,9 +145,9 @@ impl SType { .iter() .zip(t2.bind(py).iter()) .map(|(t1, t2)| -> PyResult { - t1.downcast_into::()? + t1.cast_into::()? .get() - .__eq__(t2.downcast_into::()?.get(), py) + .__eq__(t2.cast_into::()?.get(), py) }) .reduce(|res1, res2| res1.and_then(|res1| res2.map(|res2| res1 == res2))) .transpose()? @@ -174,7 +174,7 @@ impl Constant { if let Ok(bytes) = arg.extract::<&[u8]>() { return Ok(Self(constant::Constant::from(bytes.to_owned()))); } - if let Ok(tuple) = arg.downcast_exact::() { + if let Ok(tuple) = arg.cast_exact::() { return from_tuple(tuple); } if let Ok(arr) = arg.extract::>() { diff --git a/bindings/ergo-lib-python/src/lib.rs b/bindings/ergo-lib-python/src/lib.rs index 5187e7b4f..b2eb16eb4 100644 --- a/bindings/ergo-lib-python/src/lib.rs +++ b/bindings/ergo-lib-python/src/lib.rs @@ -38,7 +38,7 @@ pub(crate) fn to_value_error(e: E) -> PyErr { } pub(crate) fn from_json(json: Bound<'_, PyAny>) -> PyResult { - let res = match json.downcast_into::() { + let res = match json.cast_into::() { Ok(dict) => from_pyobject::(dict).map_err(to_value_error)?, Err(json) => { serde_json::from_str(json.into_inner().extract::<&str>()?).map_err(JsonError::from)? diff --git a/bindings/ergo-lib-wasm/src/ast/js_conv.rs b/bindings/ergo-lib-wasm/src/ast/js_conv.rs index 27183f1d8..bd38f9eb0 100644 --- a/bindings/ergo-lib-wasm/src/ast/js_conv.rs +++ b/bindings/ergo-lib-wasm/src/ast/js_conv.rs @@ -76,7 +76,7 @@ pub(crate) fn constant_from_js(val: &JsValue) -> Result { } else if let Ok(coll_longs) = coll_long_from_js(&arr) { Ok(coll_longs) } else { - return Err(ConvError::NotSupported(val.clone())); + Err(ConvError::NotSupported(val.clone())) } } else { // regular array diff --git a/ergo-chain-types/src/digest32.rs b/ergo-chain-types/src/digest32.rs index b4187196b..59b88b215 100644 --- a/ergo-chain-types/src/digest32.rs +++ b/ergo-chain-types/src/digest32.rs @@ -3,6 +3,7 @@ use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; +use base64::Engine; use core::array::TryFromSliceError; use core::convert::TryFrom; use core::convert::TryInto; @@ -46,7 +47,7 @@ impl Digest { /// Parse `Digest` from base64 encoded string pub fn from_base64(s: &str) -> Result, DigestNError> { - let bytes = base64::decode(s)?; + let bytes = base64::engine::general_purpose::STANDARD.decode(s)?; let arr: [u8; N] = bytes.as_slice().try_into()?; Ok(Digest(arr)) } diff --git a/ergo-lib/src/chain/transaction/ergo_transaction.rs b/ergo-lib/src/chain/transaction/ergo_transaction.rs index d749291f6..bf1339e2c 100644 --- a/ergo-lib/src/chain/transaction/ergo_transaction.rs +++ b/ergo-lib/src/chain/transaction/ergo_transaction.rs @@ -89,7 +89,7 @@ pub trait ErgoTransaction: ContextExtensionProvider { fn outputs(&self) -> &[ErgoBox]; /// Stateless transaction validation (no blockchain context) for a transaction - /// Returns [`Ok(())`] if validation has succeeded or returns [`TxValidationError`] + /// Returns [Ok(())] if validation has succeeded or returns [`TxValidationError`] fn validate_stateless(&self) -> Result<(), TxValidationError> { // Note that we don't need to check if inputs/data inputs/outputs are >= 1 <= 32767 here since BoundedVec takes care of that let inputs = self.inputs_ids(); diff --git a/ergo-p2p/Cargo.toml b/ergo-p2p/Cargo.toml index a56941888..b28c12525 100644 --- a/ergo-p2p/Cargo.toml +++ b/ergo-p2p/Cargo.toml @@ -27,7 +27,7 @@ tracing-error = { version = "0.2.0", features = ["traced-error"] } tokio = { workspace = true } tokio-stream = { workspace = true } tokio-util = { workspace = true } -tower = { version = "0.4.11", features = ["retry", "discover", "load", "load-shed", "timeout", "util", "buffer"] } +tower = { version = "0.5.2", features = ["retry", "discover", "load", "load-shed", "timeout", "util", "buffer"] } bytes = { workspace = true } chrono = "0.4.19" proptest = { workspace = true , optional = true } diff --git a/ergo-p2p/src/codec.rs b/ergo-p2p/src/codec.rs index a423b49ce..eb9de95c2 100644 --- a/ergo-p2p/src/codec.rs +++ b/ergo-p2p/src/codec.rs @@ -6,6 +6,7 @@ use crate::message::Request; /// Encoder/Decoder for network messages from/to bytes #[derive(Default)] +#[allow(dead_code)] pub struct Codec {} impl Encoder for Codec { diff --git a/ergo-p2p/src/constants.rs b/ergo-p2p/src/constants.rs index acd23ad7e..a89f16959 100644 --- a/ergo-p2p/src/constants.rs +++ b/ergo-p2p/src/constants.rs @@ -1,4 +1,5 @@ use std::time::Duration; /// The timeout for handshakes when connecting to new peers. +#[allow(dead_code)] pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(4); diff --git a/ergo-p2p/src/peer_connection_handler.rs b/ergo-p2p/src/peer_connection_handler.rs index ca1e98f01..2368a0f6a 100644 --- a/ergo-p2p/src/peer_connection_handler.rs +++ b/ergo-p2p/src/peer_connection_handler.rs @@ -26,6 +26,7 @@ use crate::PeerInfo; /// A service that handshakes with a remote peer and constructs a client/server pair. #[derive(Clone)] +#[allow(dead_code)] pub struct PeerConnectionHandler {} impl Service for PeerConnectionHandler { @@ -89,6 +90,7 @@ pub struct ConnectionId { direction: ConnectionDirection, } impl ConnectionId { + #[allow(dead_code)] pub(crate) fn new_outbound_direct(_addr: PeerAddr) -> Self { todo!() } diff --git a/ergo-p2p/src/peer_connector.rs b/ergo-p2p/src/peer_connector.rs index 6cdc2520f..45c7d2a05 100644 --- a/ergo-p2p/src/peer_connector.rs +++ b/ergo-p2p/src/peer_connector.rs @@ -18,12 +18,14 @@ use crate::peer_connection_handler::PeerConnectionHandler; use crate::Client; /// Opens a TCP connection before forwarding to the inner peer connection handling service for a handshake. +#[allow(dead_code)] pub struct PeerConnector { handshaker: PeerConnectionHandler, } /// A connector request. /// Contains the information needed to make an outbound connection to the peer. +#[allow(dead_code)] pub struct OutboundConnectorRequest { /// The listener address of the peer. pub addr: PeerAddr, diff --git a/ergo-rest/Cargo.toml b/ergo-rest/Cargo.toml index ca561e263..c66107e04 100644 --- a/ergo-rest/Cargo.toml +++ b/ergo-rest/Cargo.toml @@ -17,8 +17,6 @@ crate-type = ["cdylib", "rlib"] async-trait = "^0.1" bounded-integer = { workspace = true } bounded-vec = { workspace = true, features=["serde"] } -sigma-ser = { workspace = true } -sigma-util = { workspace = true } ergo-chain-types = { workspace = true } ergo-nipopow = { workspace = true } ergotree-ir = { workspace = true, features = ["json"] } @@ -26,22 +24,14 @@ ergo-merkle-tree = { workspace = true } futures = { workspace = true } thiserror = { workspace = true } derive_more = { workspace = true } -proptest-derive = {workspace = true, optional = true } rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true, optional = true } url = { workspace = true } -proptest = { workspace = true , optional = true } - -# Dependencies for `wasm-timer` -parking_lot = "0.12" -pin-utils = "0.1" -# Dependencies for `reqwest` -http = "0.2" -bytes = "1.0" -serde_urlencoded = "0.7.1" +reqwest = { version = "0.12.24", default-features = false, features = ["json"] } [target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-timer = "0.2.5" wasm-bindgen-futures = "0.4" # Dependencies for `wasm-timer` js-sys = "0.3" @@ -75,17 +65,10 @@ features = [ [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { workspace = true } tokio-stream = { workspace = true } -reqwest = { version = "0.11.10", default-features = false, features = ["json"] } [features] json = ["serde_json"] -arbitrary = ["proptest", "proptest-derive"] default = ["json"] [dev-dependencies] sigma-test-util = { workspace = true } - -# Addition from `reqwest` -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen = { version = "0.2.68", features = ["serde-serialize"] } -wasm-bindgen-test = "0.3" diff --git a/ergo-rest/src/api.rs b/ergo-rest/src/api.rs index 2ca0f4df3..e698b0553 100644 --- a/ergo-rest/src/api.rs +++ b/ergo-rest/src/api.rs @@ -1,7 +1,6 @@ //! REST API for the services in Ergo ecosystem (node, explorer, etc.) -use crate::reqwest; -use crate::reqwest::{header::CONTENT_TYPE, Client, RequestBuilder}; +use reqwest::{header::CONTENT_TYPE, Client, RequestBuilder}; use crate::NodeConf; @@ -17,7 +16,8 @@ fn set_req_headers(rb: RequestBuilder, node: NodeConf) -> RequestBuilder { fn build_client(node_conf: &NodeConf) -> Result { let builder = reqwest::Client::builder(); if let Some(t) = node_conf.timeout { - builder.timeout(t).build() + builder.build() + // builder.timeout(t).build() } else { builder.build() } diff --git a/ergo-rest/src/api/peer_discovery_internals/chrome.rs b/ergo-rest/src/api/peer_discovery_internals/chrome.rs index 1ca521af1..76becce98 100644 --- a/ergo-rest/src/api/peer_discovery_internals/chrome.rs +++ b/ergo-rest/src/api/peer_discovery_internals/chrome.rs @@ -157,7 +157,7 @@ async fn peer_discovery_impl_chrome( let rx_timeout_signal = { let (tx, rx) = futures::channel::oneshot::channel::<()>(); wasm_bindgen_futures::spawn_local(async move { - crate::wasm_timer::Delay::new(settings.global_timeout) + wasm_timer::Delay::new(settings.global_timeout) .await .expect("wasm_timer::Delay: can't spawn global timeout"); tx.send(()).unwrap(); @@ -364,7 +364,7 @@ async fn peer_discovery_impl_chrome( // pending_requests_after_timeout.len(), // ); //console_log!("Waiting 180sec for Chrome to relinquish pending HTTP requests"); - crate::wasm_timer::Delay::new(Duration::from_secs(180)).await?; + wasm_timer::Delay::new(Duration::from_secs(180)).await?; Ok(ChromePeerDiscoveryScan { active_peers, visited_peers, @@ -418,10 +418,8 @@ fn spawn_http_request_task_chrome( // This task simulates the waiting of a preflight request // that will timeout from no response. spawn_local(async move { - let _ = crate::wasm_timer::Delay::new( - Duration::from_secs(80), - ) - .await; + let _ = wasm_timer::Delay::new(Duration::from_secs(80)) + .await; let _ = tx_msg.send(Msg::PreflightRequestFailed).await; }); } else { @@ -457,11 +455,9 @@ fn spawn_http_request_task_chrome( // This task simulates the waiting of a preflight // request that will timeout from no response. spawn_local(async move { - crate::wasm_timer::Delay::new(Duration::from_secs( - 80, - )) - .await - .unwrap(); + wasm_timer::Delay::new(Duration::from_secs(80)) + .await + .unwrap(); let _ = tx_msg.send(Msg::PreflightRequestFailed).await; diff --git a/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs b/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs index 25e0f0917..af7fed519 100644 --- a/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs +++ b/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs @@ -137,7 +137,7 @@ async fn peer_discovery_impl< let rx_timeout_signal = { let (tx, rx) = futures::channel::oneshot::channel::<()>(); wasm_bindgen_futures::spawn_local(async move { - let _ = crate::wasm_timer::Delay::new(settings.global_timeout).await; + let _ = wasm_timer::Delay::new(settings.global_timeout).await; let _ = tx.send(()); }); rx.into_stream() diff --git a/ergo-rest/src/bulk_req.rs b/ergo-rest/src/bulk_req.rs index 04f022edf..eb721b75b 100644 --- a/ergo-rest/src/bulk_req.rs +++ b/ergo-rest/src/bulk_req.rs @@ -1,7 +1,7 @@ -use crate::reqwest::{Request, Response}; use ergo_chain_types::BlockId; use ergo_chain_types::PeerAddr; use ergo_nipopow::NipopowProof; +use reqwest::{Request, Response}; use crate::NodeError; diff --git a/ergo-rest/src/error.rs b/ergo-rest/src/error.rs index 0ae605d6f..073fe8471 100644 --- a/ergo-rest/src/error.rs +++ b/ergo-rest/src/error.rs @@ -1,4 +1,3 @@ -use crate::reqwest; use derive_more::From; use thiserror::Error; diff --git a/ergo-rest/src/lib.rs b/ergo-rest/src/lib.rs index 17af29259..37de4840a 100644 --- a/ergo-rest/src/lib.rs +++ b/ergo-rest/src/lib.rs @@ -28,10 +28,8 @@ mod node_conf; mod node_info; mod node_response; mod peer_info; -mod wasm_timer; pub mod api; -pub mod reqwest; pub use error::*; pub use known_nodes::KnownNodes; diff --git a/ergo-rest/src/node_conf.rs b/ergo-rest/src/node_conf.rs index 8fa34b7c0..c5ff67923 100644 --- a/ergo-rest/src/node_conf.rs +++ b/ergo-rest/src/node_conf.rs @@ -1,7 +1,7 @@ use std::time::Duration; -use crate::reqwest::header::HeaderValue; use ergo_chain_types::PeerAddr; +use reqwest::header::HeaderValue; /// Ergo node configuration #[derive(PartialEq, Eq, Debug, Clone, Copy)] diff --git a/ergo-rest/src/reqwest.rs b/ergo-rest/src/reqwest.rs deleted file mode 100644 index 7061bdf0a..000000000 --- a/ergo-rest/src/reqwest.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Thin wrapper around `reqwest` crate for WASM - -macro_rules! if_wasm { - ($($item:item)*) => {$( - #[cfg(target_arch = "wasm32")] - $item - )*} -} - -macro_rules! if_hyper { - ($($item:item)*) => {$( - #[cfg(not(target_arch = "wasm32"))] - $item - )*} -} - -pub use http::header; - -if_hyper! { - pub use reqwest::{ - Body, Client, ClientBuilder, Request, RequestBuilder, Response, Error, Result - }; -} - -if_wasm! { - mod wasm; - mod util; - #[macro_use] - mod error; - mod into_url; - mod response; - pub use self::error::{Error, Result}; - pub use self::into_url::IntoUrl; - pub use self::response::ResponseBuilderExt; - pub use http::Method; - pub use http::{StatusCode, Version}; - pub use url::Url; - pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response}; - - /// Shortcut method to quickly make a `GET` request. - /// - /// See also the methods on the [`reqwest::Response`](./struct.Response.html) - /// type. - /// - /// **NOTE**: This function creates a new internal `Client` on each call, - /// and so should not be used if making many requests. Create a - /// [`Client`](./struct.Client.html) instead. - /// - /// # Examples - /// - /// ```rust - /// # async fn run() -> Result<(), reqwest::Error> { - /// let body = reqwest::get("https://www.rust-lang.org").await? - /// .text().await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// # Errors - /// - /// This function fails if: - /// - /// - native TLS backend cannot be initialized - /// - supplied `Url` cannot be parsed - /// - there was an error while sending request - /// - redirect limit was exhausted - pub async fn get(url: T) -> crate::reqwest::Result { - Client::builder().build()?.get(url).send().await - } -} diff --git a/ergo-rest/src/reqwest/LICENSE-APACHE b/ergo-rest/src/reqwest/LICENSE-APACHE deleted file mode 100644 index 16fe87b06..000000000 --- a/ergo-rest/src/reqwest/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/ergo-rest/src/reqwest/LICENSE-MIT b/ergo-rest/src/reqwest/LICENSE-MIT deleted file mode 100644 index 4e45f06d6..000000000 --- a/ergo-rest/src/reqwest/LICENSE-MIT +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2016 Sean McArthur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/ergo-rest/src/reqwest/error.rs b/ergo-rest/src/reqwest/error.rs deleted file mode 100644 index dd9144aaa..000000000 --- a/ergo-rest/src/reqwest/error.rs +++ /dev/null @@ -1,366 +0,0 @@ -#![cfg_attr(target_arch = "wasm32", allow(unused))] -use std::error::Error as StdError; -use std::fmt; -use std::io; - -use crate::reqwest::{StatusCode, Url}; - -/// A `Result` alias where the `Err` case is `reqwest::Error`. -pub type Result = std::result::Result; - -/// The Errors that may occur when processing a `Request`. -/// -/// Note: Errors may include the full URL used to make the `Request`. If the URL -/// contains sensitive information (e.g. an API key as a query parameter), be -/// sure to remove it ([`without_url`](Error::without_url)) -pub struct Error { - inner: Box, -} - -pub(crate) type BoxError = Box; - -struct Inner { - kind: Kind, - source: Option, - url: Option, -} - -impl Error { - pub(crate) fn new(kind: Kind, source: Option) -> Error - where - E: Into, - { - Error { - inner: Box::new(Inner { - kind, - source: source.map(Into::into), - url: None, - }), - } - } - - /// Returns a possible URL related to this error. - /// - /// # Examples - /// - /// ``` - /// # async fn run() { - /// // displays last stop of a redirect loop - /// let response = reqwest::get("http://site.with.redirect.loop").await; - /// if let Err(e) = response { - /// if e.is_redirect() { - /// if let Some(final_stop) = e.url() { - /// println!("redirect loop at {}", final_stop); - /// } - /// } - /// } - /// # } - /// ``` - pub fn url(&self) -> Option<&Url> { - self.inner.url.as_ref() - } - - /// Returns a mutable referene to the URL related to this error - /// - /// This is useful if you need to remove sensitive information from the URL - /// (e.g. an API key in the query), but do not want to remove the URL - /// entirely. - pub fn url_mut(&mut self) -> Option<&mut Url> { - self.inner.url.as_mut() - } - - /// Add a url related to this error (overwriting any existing) - pub fn with_url(mut self, url: Url) -> Self { - self.inner.url = Some(url); - self - } - - /// Strip the related url from this error (if, for example, it contains - /// sensitive information) - pub fn without_url(mut self) -> Self { - self.inner.url = None; - self - } - - /// Returns true if the error is from a type Builder. - pub fn is_builder(&self) -> bool { - matches!(self.inner.kind, Kind::Builder) - } - - /// Returns true if the error is from a `RedirectPolicy`. - pub fn is_redirect(&self) -> bool { - matches!(self.inner.kind, Kind::Redirect) - } - - /// Returns true if the error is from `Response::error_for_status`. - pub fn is_status(&self) -> bool { - matches!(self.inner.kind, Kind::Status(_)) - } - - /// Returns true if the error is related to a timeout. - pub fn is_timeout(&self) -> bool { - let mut source = self.source(); - - while let Some(err) = source { - if err.is::() { - return true; - } - source = err.source(); - } - - false - } - - /// Returns true if the error is related to the request - pub fn is_request(&self) -> bool { - matches!(self.inner.kind, Kind::Request) - } - - #[cfg(not(target_arch = "wasm32"))] - /// Returns true if the error is related to connect - pub fn is_connect(&self) -> bool { - let mut source = self.source(); - - while let Some(err) = source { - if let Some(hyper_err) = err.downcast_ref::() { - if hyper_err.is_connect() { - return true; - } - } - - source = err.source(); - } - - false - } - - /// Returns true if the error is related to the request or response body - pub fn is_body(&self) -> bool { - matches!(self.inner.kind, Kind::Body) - } - - /// Returns true if the error is related to decoding the response's body - pub fn is_decode(&self) -> bool { - matches!(self.inner.kind, Kind::Decode) - } - - /// Returns the status code, if the error was generated from a response. - pub fn status(&self) -> Option { - match self.inner.kind { - Kind::Status(code) => Some(code), - _ => None, - } - } - - // private - - #[allow(unused)] - pub(crate) fn into_io(self) -> io::Error { - io::Error::new(io::ErrorKind::Other, self) - } -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut builder = f.debug_struct("reqwest::Error"); - - builder.field("kind", &self.inner.kind); - - if let Some(ref url) = self.inner.url { - builder.field("url", url); - } - if let Some(ref source) = self.inner.source { - builder.field("source", source); - } - - builder.finish() - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.inner.kind { - Kind::Builder => f.write_str("builder error")?, - Kind::Request => f.write_str("error sending request")?, - Kind::Body => f.write_str("request or response body error")?, - Kind::Decode => f.write_str("error decoding response body")?, - Kind::Redirect => f.write_str("error following redirect")?, - Kind::Status(ref code) => { - let prefix = if code.is_client_error() { - "HTTP status client error" - } else { - debug_assert!(code.is_server_error()); - "HTTP status server error" - }; - write!(f, "{} ({})", prefix, code)?; - } - }; - - if let Some(url) = &self.inner.url { - write!(f, " for url ({})", url.as_str())?; - } - - if let Some(e) = &self.inner.source { - write!(f, ": {}", e)?; - } - - Ok(()) - } -} - -impl StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - self.inner.source.as_ref().map(|e| &**e as _) - } -} - -#[cfg(target_arch = "wasm32")] -impl From for wasm_bindgen::JsValue { - fn from(err: Error) -> wasm_bindgen::JsValue { - js_sys::Error::from(err).into() - } -} - -#[cfg(target_arch = "wasm32")] -impl From for js_sys::Error { - fn from(err: Error) -> js_sys::Error { - js_sys::Error::new(&format!("{}", err)) - } -} - -#[derive(Debug)] -pub(crate) enum Kind { - Builder, - Request, - Redirect, - Status(StatusCode), - Body, - Decode, -} - -// constructors - -pub(crate) fn builder>(e: E) -> Error { - Error::new(Kind::Builder, Some(e)) -} - -pub(crate) fn body>(e: E) -> Error { - Error::new(Kind::Body, Some(e)) -} - -pub(crate) fn decode>(e: E) -> Error { - Error::new(Kind::Decode, Some(e)) -} - -pub(crate) fn request>(e: E) -> Error { - Error::new(Kind::Request, Some(e)) -} - -pub(crate) fn redirect>(e: E, url: Url) -> Error { - Error::new(Kind::Redirect, Some(e)).with_url(url) -} - -pub(crate) fn status_code(url: Url, status: StatusCode) -> Error { - Error::new(Kind::Status(status), None::).with_url(url) -} - -pub(crate) fn url_bad_scheme(url: Url) -> Error { - Error::new(Kind::Builder, Some("URL scheme is not allowed")).with_url(url) -} - -if_wasm! { - pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError { - format!("{:?}", js_val).into() - } -} - -// io::Error helpers - -#[allow(unused)] -pub(crate) fn into_io(e: Error) -> io::Error { - e.into_io() -} - -#[allow(unused)] -pub(crate) fn decode_io(e: io::Error) -> Error { - if e.get_ref().map(|r| r.is::()).unwrap_or(false) { - *e.into_inner() - .expect("io::Error::get_ref was Some(_)") - .downcast::() - .expect("StdError::is() was true") - } else { - decode(e) - } -} - -// internal Error "sources" - -#[derive(Debug)] -pub(crate) struct TimedOut; - -impl fmt::Display for TimedOut { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("operation timed out") - } -} - -impl StdError for TimedOut {} - -#[cfg(test)] -mod tests { - use super::*; - - fn assert_send() {} - fn assert_sync() {} - - #[test] - fn test_source_chain() { - let root = Error::new(Kind::Request, None::); - assert!(root.source().is_none()); - - let link = super::body(root); - assert!(link.source().is_some()); - assert_send::(); - assert_sync::(); - } - - #[test] - fn mem_size_of() { - use std::mem::size_of; - assert_eq!(size_of::(), size_of::()); - } - - #[test] - fn roundtrip_io_error() { - let orig = super::request("orig"); - // Convert reqwest::Error into an io::Error... - let io = orig.into_io(); - // Convert that io::Error back into a reqwest::Error... - let err = super::decode_io(io); - // It should have pulled out the original, not nested it... - match err.inner.kind { - Kind::Request => (), - _ => panic!("{:?}", err), - } - } - - #[test] - fn from_unknown_io_error() { - let orig = io::Error::new(io::ErrorKind::Other, "orly"); - let err = super::decode_io(orig); - match err.inner.kind { - Kind::Decode => (), - _ => panic!("{:?}", err), - } - } - - #[test] - fn is_timeout() { - let err = super::request(super::TimedOut); - assert!(err.is_timeout()); - - let io = io::Error::new(io::ErrorKind::Other, err); - let nested = super::request(io); - assert!(nested.is_timeout()); - } -} diff --git a/ergo-rest/src/reqwest/into_url.rs b/ergo-rest/src/reqwest/into_url.rs deleted file mode 100644 index 19e4463cf..000000000 --- a/ergo-rest/src/reqwest/into_url.rs +++ /dev/null @@ -1,92 +0,0 @@ -use url::Url; - -/// A trait to try to convert some type into a `Url`. -/// -/// This trait is "sealed", such that only types within reqwest can -/// implement it. -pub trait IntoUrl: IntoUrlSealed {} - -impl IntoUrl for Url {} -impl IntoUrl for String {} -impl<'a> IntoUrl for &'a str {} -impl<'a> IntoUrl for &'a String {} - -pub trait IntoUrlSealed { - // Besides parsing as a valid `Url`, the `Url` must be a valid - // `http::Uri`, in that it makes sense to use in a network request. - fn into_url(self) -> crate::reqwest::Result; - - fn as_str(&self) -> &str; -} - -impl IntoUrlSealed for Url { - fn into_url(self) -> crate::reqwest::Result { - if self.has_host() { - Ok(self) - } else { - Err(crate::reqwest::error::url_bad_scheme(self)) - } - } - - fn as_str(&self) -> &str { - self.as_ref() - } -} - -impl<'a> IntoUrlSealed for &'a str { - fn into_url(self) -> crate::reqwest::Result { - Url::parse(self) - .map_err(crate::reqwest::error::builder)? - .into_url() - } - - fn as_str(&self) -> &str { - self - } -} - -impl<'a> IntoUrlSealed for &'a String { - fn into_url(self) -> crate::reqwest::Result { - (&**self).into_url() - } - - fn as_str(&self) -> &str { - self.as_ref() - } -} - -impl<'a> IntoUrlSealed for String { - fn into_url(self) -> crate::reqwest::Result { - (&*self).into_url() - } - - fn as_str(&self) -> &str { - self.as_ref() - } -} - -if_hyper! { - pub(crate) fn expect_uri(url: &Url) -> http::Uri { - url.as_str() - .parse() - .expect("a parsed Url should always be a valid Uri") - } - - pub(crate) fn try_uri(url: &Url) -> Option { - url.as_str().parse().ok() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn into_url_file_scheme() { - let err = "file:///etc/hosts".into_url().unwrap_err(); - assert_eq!( - err.to_string(), - "builder error for url (file:///etc/hosts): URL scheme is not allowed" - ); - } -} diff --git a/ergo-rest/src/reqwest/response.rs b/ergo-rest/src/reqwest/response.rs deleted file mode 100644 index 9c92cba53..000000000 --- a/ergo-rest/src/reqwest/response.rs +++ /dev/null @@ -1,41 +0,0 @@ -use url::Url; - -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct ResponseUrl(pub Url); - -/// Extension trait for http::response::Builder objects -/// -/// Allows the user to add a `Url` to the http::Response -pub trait ResponseBuilderExt { - /// A builder method for the `http::response::Builder` type that allows the user to add a `Url` - /// to the `http::Response` - fn url(self, url: Url) -> Self; -} - -impl ResponseBuilderExt for http::response::Builder { - fn url(self, url: Url) -> Self { - self.extension(ResponseUrl(url)) - } -} - -#[cfg(test)] -mod tests { - use super::{ResponseBuilderExt, ResponseUrl}; - use http::response::Builder; - use url::Url; - - #[test] - fn test_response_builder_ext() { - let url = Url::parse("http://example.com").unwrap(); - let response = Builder::new() - .status(200) - .url(url.clone()) - .body(()) - .unwrap(); - - assert_eq!( - response.extensions().get::(), - Some(&ResponseUrl(url)) - ); - } -} diff --git a/ergo-rest/src/reqwest/util.rs b/ergo-rest/src/reqwest/util.rs deleted file mode 100644 index 09e9fd308..000000000 --- a/ergo-rest/src/reqwest/util.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::reqwest::header::{Entry, HeaderMap, OccupiedEntry}; - -pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) { - // IntoIter of HeaderMap yields (Option, HeaderValue). - // The first time a name is yielded, it will be Some(name), and if - // there are more values with the same name, the next yield will be - // None. - - let mut prev_entry: Option> = None; - for (key, value) in src { - match key { - Some(key) => match dst.entry(key) { - Entry::Occupied(mut e) => { - e.insert(value); - prev_entry = Some(e); - } - Entry::Vacant(e) => { - let e = e.insert_entry(value); - prev_entry = Some(e); - } - }, - None => match prev_entry { - Some(ref mut entry) => { - entry.append(value); - } - None => unreachable!("HeaderMap::into_iter yielded None first"), - }, - } - } -} diff --git a/ergo-rest/src/reqwest/wasm/body.rs b/ergo-rest/src/reqwest/wasm/body.rs deleted file mode 100644 index faf5d77f2..000000000 --- a/ergo-rest/src/reqwest/wasm/body.rs +++ /dev/null @@ -1,292 +0,0 @@ -#![allow(clippy::unused_unit)] -#[cfg(feature = "multipart")] -use super::multipart::Form; -/// dox -use bytes::Bytes; -use js_sys::Uint8Array; -use std::fmt; -use wasm_bindgen::JsValue; - -/// The body of a `Request`. -/// -/// In most cases, this is not needed directly, as the -/// [`RequestBuilder.body`][builder] method uses `Into`, which allows -/// passing many things (like a string or vector of bytes). -/// -/// [builder]: ./struct.RequestBuilder.html#method.body -pub struct Body { - inner: Inner, -} - -enum Inner { - Bytes(Bytes), - /// MultipartForm holds a multipart/form-data body. - #[cfg(feature = "multipart")] - MultipartForm(Form), - /// MultipartPart holds the body of a multipart/form-data part. - #[cfg(feature = "multipart")] - MultipartPart(Bytes), -} - -impl Body { - /// Returns a reference to the internal data of the `Body`. - /// - /// `None` is returned, if the underlying data is a multipart form. - #[inline] - pub fn as_bytes(&self) -> Option<&[u8]> { - match &self.inner { - Inner::Bytes(bytes) => Some(bytes.as_ref()), - #[cfg(feature = "multipart")] - Inner::MultipartForm(_) => None, - #[cfg(feature = "multipart")] - Inner::MultipartPart(bytes) => Some(bytes.as_ref()), - } - } - pub(crate) fn to_js_value(&self) -> crate::reqwest::Result { - match &self.inner { - Inner::Bytes(body_bytes) => { - let body_bytes: &[u8] = body_bytes.as_ref(); - let body_uint8_array: Uint8Array = body_bytes.into(); - let js_value: &JsValue = body_uint8_array.as_ref(); - Ok(js_value.to_owned()) - } - #[cfg(feature = "multipart")] - Inner::MultipartForm(form) => { - let form_data = form.to_form_data()?; - let js_value: &JsValue = form_data.as_ref(); - Ok(js_value.to_owned()) - } - #[cfg(feature = "multipart")] - Inner::MultipartPart(body_bytes) => { - let body_bytes: &[u8] = body_bytes.as_ref(); - let body_uint8_array: Uint8Array = body_bytes.into(); - let body_array = js_sys::Array::new(); - body_array.push(&body_uint8_array); - let js_value: &JsValue = body_array.as_ref(); - Ok(js_value.to_owned()) - } - } - } - - #[inline] - #[cfg(feature = "multipart")] - pub(crate) fn from_form(f: Form) -> Body { - Self { - inner: Inner::MultipartForm(f), - } - } - - /// into_part turns a regular body into the body of a mutlipart/form-data part. - #[cfg(feature = "multipart")] - pub(crate) fn into_part(self) -> Body { - match self.inner { - Inner::Bytes(bytes) => Self { - inner: Inner::MultipartPart(bytes), - }, - Inner::MultipartForm(form) => Self { - inner: Inner::MultipartForm(form), - }, - Inner::MultipartPart(bytes) => Self { - inner: Inner::MultipartPart(bytes), - }, - } - } - - pub(crate) fn is_empty(&self) -> bool { - match &self.inner { - Inner::Bytes(bytes) => bytes.is_empty(), - #[cfg(feature = "multipart")] - Inner::MultipartForm(form) => form.is_empty(), - #[cfg(feature = "multipart")] - Inner::MultipartPart(bytes) => bytes.is_empty(), - } - } - - pub(crate) fn try_clone(&self) -> Option { - match &self.inner { - Inner::Bytes(bytes) => Some(Self { - inner: Inner::Bytes(bytes.clone()), - }), - #[cfg(feature = "multipart")] - Inner::MultipartForm(_) => None, - #[cfg(feature = "multipart")] - Inner::MultipartPart(bytes) => Some(Self { - inner: Inner::MultipartPart(bytes.clone()), - }), - } - } -} - -impl From for Body { - #[inline] - fn from(bytes: Bytes) -> Body { - Body { - inner: Inner::Bytes(bytes), - } - } -} - -impl From> for Body { - #[inline] - fn from(vec: Vec) -> Body { - Body { - inner: Inner::Bytes(vec.into()), - } - } -} - -impl From<&'static [u8]> for Body { - #[inline] - fn from(s: &'static [u8]) -> Body { - Body { - inner: Inner::Bytes(Bytes::from_static(s)), - } - } -} - -impl From for Body { - #[inline] - fn from(s: String) -> Body { - Body { - inner: Inner::Bytes(s.into()), - } - } -} - -impl From<&'static str> for Body { - #[inline] - fn from(s: &'static str) -> Body { - s.as_bytes().into() - } -} - -impl fmt::Debug for Body { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Body").finish() - } -} - -#[cfg(test)] -mod tests { - use crate::reqwest::Body; - use js_sys::Uint8Array; - use wasm_bindgen::prelude::*; - use wasm_bindgen_test::*; - - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - - #[wasm_bindgen] - extern "C" { - // Use `js_namespace` here to bind `console.log(..)` instead of just - // `log(..)` - #[wasm_bindgen(js_namespace = console)] - fn log(s: String); - } - - #[wasm_bindgen_test] - async fn test_body() { - let body = Body::from("TEST"); - assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap()); - } - - #[wasm_bindgen_test] - async fn test_body_js_static_str() { - let body_value = "TEST"; - let body = Body::from(body_value); - - let mut init = web_sys::RequestInit::new(); - init.method("POST"); - init.body(Some( - body.to_js_value() - .expect("could not convert body to JsValue") - .as_ref(), - )); - - let js_req = web_sys::Request::new_with_str_and_init("", &init) - .expect("could not create JS request"); - let text_promise = js_req.text().expect("could not get text promise"); - let text = crate::wasm::promise::(text_promise) - .await - .expect("could not get request body as text"); - - assert_eq!(text.as_string().expect("text is not a string"), body_value); - } - #[wasm_bindgen_test] - async fn test_body_js_string() { - let body_value = "TEST".to_string(); - let body = Body::from(body_value.clone()); - - let mut init = web_sys::RequestInit::new(); - init.method("POST"); - init.body(Some( - body.to_js_value() - .expect("could not convert body to JsValue") - .as_ref(), - )); - - let js_req = web_sys::Request::new_with_str_and_init("", &init) - .expect("could not create JS request"); - let text_promise = js_req.text().expect("could not get text promise"); - let text = crate::wasm::promise::(text_promise) - .await - .expect("could not get request body as text"); - - assert_eq!(text.as_string().expect("text is not a string"), body_value); - } - - #[wasm_bindgen_test] - async fn test_body_js_static_u8_slice() { - let body_value: &'static [u8] = b"\x00\x42"; - let body = Body::from(body_value); - - let mut init = web_sys::RequestInit::new(); - init.method("POST"); - init.body(Some( - body.to_js_value() - .expect("could not convert body to JsValue") - .as_ref(), - )); - - let js_req = web_sys::Request::new_with_str_and_init("", &init) - .expect("could not create JS request"); - - let array_buffer_promise = js_req - .array_buffer() - .expect("could not get array_buffer promise"); - let array_buffer = crate::wasm::promise::(array_buffer_promise) - .await - .expect("could not get request body as array buffer"); - - let v = Uint8Array::new(&array_buffer).to_vec(); - - assert_eq!(v, body_value); - } - - #[wasm_bindgen_test] - async fn test_body_js_vec_u8() { - let body_value = vec![0u8, 42]; - let body = Body::from(body_value.clone()); - - let mut init = web_sys::RequestInit::new(); - init.method("POST"); - init.body(Some( - body.to_js_value() - .expect("could not convert body to JsValue") - .as_ref(), - )); - - let js_req = web_sys::Request::new_with_str_and_init("", &init) - .expect("could not create JS request"); - - let array_buffer_promise = js_req - .array_buffer() - .expect("could not get array_buffer promise"); - let array_buffer = crate::wasm::promise::(array_buffer_promise) - .await - .expect("could not get request body as array buffer"); - - let v = Uint8Array::new(&array_buffer).to_vec(); - - assert_eq!(v, body_value); - } -} diff --git a/ergo-rest/src/reqwest/wasm/client.rs b/ergo-rest/src/reqwest/wasm/client.rs deleted file mode 100644 index a5a8f55b4..000000000 --- a/ergo-rest/src/reqwest/wasm/client.rs +++ /dev/null @@ -1,427 +0,0 @@ -#![allow(clippy::unused_unit)] -use http::{HeaderMap, Method}; -use js_sys::{Promise, JSON}; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, future::Future, sync::Arc}; -use url::Url; -use wasm_bindgen::prelude::{wasm_bindgen, Closure, UnwrapThrowExt as _}; -use wasm_bindgen::JsCast; - -use super::{Request, RequestBuilder, Response}; -use crate::reqwest::IntoUrl; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_name = fetch)] - fn fetch_with_request(input: &web_sys::Request) -> Promise; - - #[wasm_bindgen(js_namespace = console, js_name = log)] - fn debug(s: &str); -} - -fn js_fetch(req: &web_sys::Request) -> Promise { - use wasm_bindgen::{JsCast, JsValue}; - let global = js_sys::global(); - - if let Ok(true) = js_sys::Reflect::has(&global, &JsValue::from_str("ServiceWorkerGlobalScope")) - { - global - .unchecked_into::() - .fetch_with_request(req) - } else { - // browser - fetch_with_request(req) - } -} - -/// dox -#[derive(Clone)] -pub struct Client { - config: Arc, - timeout: Option, -} - -/// dox -pub struct ClientBuilder { - config: Config, - timeout: Option, -} - -impl Client { - /// dox - pub fn new() -> Self { - Client::builder().build().unwrap_throw() - } - - /// dox - pub fn builder() -> ClientBuilder { - ClientBuilder::new() - } - - /// Convenience method to make a `GET` request to a URL. - /// - /// # Errors - /// - /// This method fails whenever supplied `Url` cannot be parsed. - pub fn get(&self, url: U) -> RequestBuilder { - self.request(Method::GET, url) - } - - /// Convenience method to make a `POST` request to a URL. - /// - /// # Errors - /// - /// This method fails whenever supplied `Url` cannot be parsed. - pub fn post(&self, url: U) -> RequestBuilder { - self.request(Method::POST, url) - } - - /// Convenience method to make a `PUT` request to a URL. - /// - /// # Errors - /// - /// This method fails whenever supplied `Url` cannot be parsed. - pub fn put(&self, url: U) -> RequestBuilder { - self.request(Method::PUT, url) - } - - /// Convenience method to make a `PATCH` request to a URL. - /// - /// # Errors - /// - /// This method fails whenever supplied `Url` cannot be parsed. - pub fn patch(&self, url: U) -> RequestBuilder { - self.request(Method::PATCH, url) - } - - /// Convenience method to make a `DELETE` request to a URL. - /// - /// # Errors - /// - /// This method fails whenever supplied `Url` cannot be parsed. - pub fn delete(&self, url: U) -> RequestBuilder { - self.request(Method::DELETE, url) - } - - /// Convenience method to make a `HEAD` request to a URL. - /// - /// # Errors - /// - /// This method fails whenever supplied `Url` cannot be parsed. - pub fn head(&self, url: U) -> RequestBuilder { - self.request(Method::HEAD, url) - } - - /// Start building a `Request` with the `Method` and `Url`. - /// - /// Returns a `RequestBuilder`, which will allow setting headers and - /// request body before sending. - /// - /// # Errors - /// - /// This method fails whenever supplied `Url` cannot be parsed. - pub fn request(&self, method: Method, url: U) -> RequestBuilder { - let req = url.into_url().map(move |url| Request::new(method, url)); - let builder = RequestBuilder::new(self.clone(), req); - if let Some(t) = self.timeout { - builder.timeout(t) - } else { - builder - } - } - - /// Executes a `Request`. - /// - /// A `Request` can be built manually with `Request::new()` or obtained - /// from a RequestBuilder with `RequestBuilder::build()`. - /// - /// You should prefer to use the `RequestBuilder` and - /// `RequestBuilder::send()`. - /// - /// # Errors - /// - /// This method fails if there was an error while sending request, - /// redirect loop was detected or redirect limit was exhausted. - pub fn execute( - &self, - request: Request, - ) -> impl Future> { - self.execute_request(request) - } - - // merge request headers with Client default_headers, prior to external http fetch - fn merge_headers(&self, req: &mut Request) { - use http::header::Entry; - let headers: &mut HeaderMap = req.headers_mut(); - // insert default headers in the request headers - // without overwriting already appended headers. - for (key, value) in self.config.headers.iter() { - if let Entry::Vacant(entry) = headers.entry(key) { - entry.insert(value.clone()); - } - } - } - - pub(super) fn execute_request( - &self, - mut req: Request, - ) -> impl Future> { - self.merge_headers(&mut req); - fetch(req) - } -} - -impl Default for Client { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Debug for Client { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut builder = f.debug_struct("Client"); - self.config.fmt_fields(&mut builder); - builder.finish() - } -} - -impl fmt::Debug for ClientBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut builder = f.debug_struct("ClientBuilder"); - self.config.fmt_fields(&mut builder); - builder.finish() - } -} - -async fn fetch(req: Request) -> crate::reqwest::Result { - // Build the js Request - let mut init = web_sys::RequestInit::new(); - let abort_controller = Rc::new(web_sys::AbortController::new().unwrap()); - let abort_signal = Rc::clone(&abort_controller).signal(); - let window = web_sys::window().expect("should have a window in this context"); - init.method(req.method().as_str()); - - // convert HeaderMap to Headers - let js_headers = web_sys::Headers::new() - .map_err(crate::reqwest::error::wasm) - .map_err(crate::reqwest::error::builder)?; - - for (name, value) in req.headers() { - js_headers - .append( - name.as_str(), - value.to_str().map_err(crate::reqwest::error::builder)?, - ) - .map_err(crate::reqwest::error::wasm) - .map_err(crate::reqwest::error::builder)?; - } - init.headers(&js_headers.into()); - - // When req.cors is true, do nothing because the default mode is 'cors' - if !req.cors { - init.mode(web_sys::RequestMode::NoCors); - } - - if let Some(creds) = req.credentials { - init.credentials(creds); - } - - if let Some(body) = req.body() { - if !body.is_empty() { - init.body(Some(body.to_js_value()?.as_ref())); - } - } - - let timeout_handle = if let Some(duration) = req.timeout() { - let abort_request_cb = Closure::wrap(Box::new(move || { - abort_controller.abort(); - }) as Box); - - init.signal(Some(&abort_signal)); - - let handle = window - .set_timeout_with_callback_and_timeout_and_arguments_0( - abort_request_cb.as_ref().unchecked_ref(), - duration.as_millis() as i32, - ) - .expect("timeout was set"); - - abort_request_cb.forget(); - Some(handle) - } else { - None - }; - - let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init) - .map_err(crate::reqwest::error::wasm) - .map_err(crate::reqwest::error::builder)?; - - // Await the fetch() promise - let p = js_fetch(&js_req); - let js_resp = super::promise::(p) - .await - .map_err(crate::reqwest::error::request)?; - - if let Some(handle) = timeout_handle { - window.clear_timeout_with_handle(handle); - } - - // Convert from the js Response - let mut resp = http::Response::builder().status(js_resp.status()); - - let url = Url::parse(&js_resp.url()).expect_throw("url parse"); - - let js_headers = js_resp.headers(); - let js_iter = js_sys::try_iter(&js_headers) - .expect_throw("headers try_iter") - .expect_throw("headers have an iterator"); - - for item in js_iter { - let item = item.expect_throw("headers iterator doesn't throw"); - let serialized_headers: String = JSON::stringify(&item) - .expect_throw("serialized headers") - .into(); - let [name, value]: [String; 2] = serde_json::from_str(&serialized_headers) - .expect_throw("deserializable serialized headers"); - resp = resp.header(&name, &value); - } - - resp.body(js_resp) - .map(|resp| Response::new(resp, url)) - .map_err(crate::reqwest::error::request) -} - -// ===== impl ClientBuilder ===== - -impl ClientBuilder { - /// dox - pub fn new() -> Self { - ClientBuilder { - config: Config::default(), - timeout: Some(Duration::from_secs(30)), - } - } - - /// Returns a 'Client' that uses this ClientBuilder configuration - pub fn build(mut self) -> Result { - let config = std::mem::take(&mut self.config); - Ok(Client { - config: Arc::new(config), - timeout: self.timeout, - }) - } - - /// Sets the default headers for every request - pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder { - for (key, value) in headers.iter() { - self.config.headers.insert(key, value.clone()); - } - self - } - - /// Set a timeout for connect, read and write operations of a `Client`. - /// - /// Default is 30 seconds. - pub fn timeout(mut self, timeout: Duration) -> ClientBuilder { - self.timeout = Some(timeout); - self - } -} - -impl Default for ClientBuilder { - fn default() -> Self { - Self::new() - } -} - -#[derive(Clone, Debug)] -struct Config { - headers: HeaderMap, -} - -impl Default for Config { - fn default() -> Config { - Config { - headers: HeaderMap::new(), - } - } -} - -impl Config { - fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) { - f.field("default_headers", &self.headers); - } -} - -#[cfg(test)] -mod tests { - use wasm_bindgen_test::*; - - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - - #[wasm_bindgen_test] - async fn default_headers() { - use crate::reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; - - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet")); - let client = crate::Client::builder() - .default_headers(headers) - .build() - .expect("client"); - let mut req = client - .get("https://www.example.com") - .build() - .expect("request"); - // merge headers as if client were about to issue fetch - client.merge_headers(&mut req); - - let test_headers = req.headers(); - assert!(test_headers.get(CONTENT_TYPE).is_some(), "content-type"); - assert!(test_headers.get("x-custom").is_some(), "custom header"); - assert!(test_headers.get("accept").is_none(), "no accept header"); - } - - #[wasm_bindgen_test] - async fn default_headers_clone() { - use crate::reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; - - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet")); - let client = crate::Client::builder() - .default_headers(headers) - .build() - .expect("client"); - - let mut req = client - .get("https://www.example.com") - .header(CONTENT_TYPE, "text/plain") - .build() - .expect("request"); - client.merge_headers(&mut req); - let headers1 = req.headers(); - - // confirm that request headers override defaults - assert_eq!( - headers1.get(CONTENT_TYPE).unwrap(), - "text/plain", - "request headers override defaults" - ); - - // confirm that request headers don't change client defaults - let mut req2 = client - .get("https://www.example.com/x") - .build() - .expect("req 2"); - client.merge_headers(&mut req2); - let headers2 = req2.headers(); - assert_eq!( - headers2.get(CONTENT_TYPE).unwrap(), - "application/json", - "request headers don't change client defaults" - ); - } -} diff --git a/ergo-rest/src/reqwest/wasm/mod.rs b/ergo-rest/src/reqwest/wasm/mod.rs deleted file mode 100644 index ea9a045d4..000000000 --- a/ergo-rest/src/reqwest/wasm/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use wasm_bindgen::JsCast; - -mod body; -mod client; -/// TODO -#[cfg(feature = "multipart")] -pub mod multipart; -mod request; -mod response; - -pub use self::body::Body; -pub use self::client::{Client, ClientBuilder}; -pub use self::request::{Request, RequestBuilder}; -pub use self::response::Response; - -async fn promise(promise: js_sys::Promise) -> Result -where - T: JsCast, -{ - use wasm_bindgen_futures::JsFuture; - - let js_val = JsFuture::from(promise).await.map_err(crate::reqwest::error::wasm)?; - - js_val - .dyn_into::() - .map_err(|_js_val| "promise resolved to unexpected type".into()) -} diff --git a/ergo-rest/src/reqwest/wasm/multipart.rs b/ergo-rest/src/reqwest/wasm/multipart.rs deleted file mode 100644 index 909875c0a..000000000 --- a/ergo-rest/src/reqwest/wasm/multipart.rs +++ /dev/null @@ -1,360 +0,0 @@ -//! multipart/form-data -use std::borrow::Cow; -use std::fmt; - -use http::HeaderMap; -use mime_guess::Mime; -use web_sys::FormData; - -use super::Body; - -/// An async multipart/form-data request. -pub struct Form { - inner: FormParts, -} - -impl Form { - pub(crate) fn is_empty(&self) -> bool { - self.inner.fields.is_empty() - } -} - -/// A field in a multipart form. -pub struct Part { - meta: PartMetadata, - value: Body, -} - -pub(crate) struct FormParts

{ - pub(crate) fields: Vec<(Cow<'static, str>, P)>, -} - -pub(crate) struct PartMetadata { - mime: Option, - file_name: Option>, - pub(crate) headers: HeaderMap, -} - -pub(crate) trait PartProps { - fn metadata(&self) -> &PartMetadata; -} - -// ===== impl Form ===== - -impl Default for Form { - fn default() -> Self { - Self::new() - } -} - -impl Form { - /// Creates a new async Form without any content. - pub fn new() -> Form { - Form { - inner: FormParts::new(), - } - } - - /// Add a data field with supplied name and value. - /// - /// # Examples - /// - /// ``` - /// let form = reqwest::multipart::Form::new() - /// .text("username", "seanmonstar") - /// .text("password", "secret"); - /// ``` - pub fn text(self, name: T, value: U) -> Form - where - T: Into>, - U: Into>, - { - self.part(name, Part::text(value)) - } - - /// Adds a customized Part. - pub fn part(self, name: T, part: Part) -> Form - where - T: Into>, - { - self.with_inner(move |inner| inner.part(name, part)) - } - - fn with_inner(self, func: F) -> Self - where - F: FnOnce(FormParts) -> FormParts, - { - Form { - inner: func(self.inner), - } - } - - pub(crate) fn to_form_data(&self) -> crate::Result { - let form = FormData::new() - .map_err(crate::error::wasm) - .map_err(crate::error::builder)?; - - for (name, part) in self.inner.fields.iter() { - let blob = part.blob()?; - - if let Some(file_name) = &part.metadata().file_name { - form.append_with_blob_and_filename(name, &blob, &file_name) - } else { - form.append_with_blob(name, &blob) - } - .map_err(crate::error::wasm) - .map_err(crate::error::builder)?; - } - Ok(form) - } -} - -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt_fields("Form", f) - } -} - -// ===== impl Part ===== - -impl Part { - /// Makes a text parameter. - pub fn text(value: T) -> Part - where - T: Into>, - { - let body = match value.into() { - Cow::Borrowed(slice) => Body::from(slice), - Cow::Owned(string) => Body::from(string), - }; - Part::new(body) - } - - /// Makes a new parameter from arbitrary bytes. - pub fn bytes(value: T) -> Part - where - T: Into>, - { - let body = match value.into() { - Cow::Borrowed(slice) => Body::from(slice), - Cow::Owned(vec) => Body::from(vec), - }; - Part::new(body) - } - - /// Makes a new parameter from an arbitrary stream. - pub fn stream>(value: T) -> Part { - Part::new(value.into()) - } - - fn new(value: Body) -> Part { - Part { - meta: PartMetadata::new(), - value: value.into_part(), - } - } - - /// Tries to set the mime of this part. - pub fn mime_str(self, mime: &str) -> crate::Result { - Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) - } - - // Re-export when mime 0.4 is available, with split MediaType/MediaRange. - fn mime(self, mime: Mime) -> Part { - self.with_inner(move |inner| inner.mime(mime)) - } - - /// Sets the filename, builder style. - pub fn file_name(self, filename: T) -> Part - where - T: Into>, - { - self.with_inner(move |inner| inner.file_name(filename)) - } - - fn with_inner(self, func: F) -> Self - where - F: FnOnce(PartMetadata) -> PartMetadata, - { - Part { - meta: func(self.meta), - value: self.value, - } - } - - fn blob(&self) -> crate::Result { - use web_sys::Blob; - use web_sys::BlobPropertyBag; - let mut properties = BlobPropertyBag::new(); - if let Some(mime) = &self.meta.mime { - properties.type_(mime.as_ref()); - } - - // BUG: the return value of to_js_value() is not valid if - // it is a MultipartForm variant. - let js_value = self.value.to_js_value()?; - Blob::new_with_u8_array_sequence_and_options(&js_value, &properties) - .map_err(crate::error::wasm) - .map_err(crate::error::builder) - } -} - -impl fmt::Debug for Part { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut dbg = f.debug_struct("Part"); - dbg.field("value", &self.value); - self.meta.fmt_fields(&mut dbg); - dbg.finish() - } -} - -impl PartProps for Part { - fn metadata(&self) -> &PartMetadata { - &self.meta - } -} - -// ===== impl FormParts ===== - -impl FormParts

{ - pub(crate) fn new() -> Self { - FormParts { fields: Vec::new() } - } - - /// Adds a customized Part. - pub(crate) fn part(mut self, name: T, part: P) -> Self - where - T: Into>, - { - self.fields.push((name.into(), part)); - self - } -} - -impl FormParts

{ - pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct(ty_name) - .field("parts", &self.fields) - .finish() - } -} - -// ===== impl PartMetadata ===== - -impl PartMetadata { - pub(crate) fn new() -> Self { - PartMetadata { - mime: None, - file_name: None, - headers: HeaderMap::default(), - } - } - - pub(crate) fn mime(mut self, mime: Mime) -> Self { - self.mime = Some(mime); - self - } - - pub(crate) fn file_name(mut self, filename: T) -> Self - where - T: Into>, - { - self.file_name = Some(filename.into()); - self - } -} - -impl PartMetadata { - pub(crate) fn fmt_fields<'f, 'fa, 'fb>( - &self, - debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, - ) -> &'f mut fmt::DebugStruct<'fa, 'fb> { - debug_struct - .field("mime", &self.mime) - .field("file_name", &self.file_name) - .field("headers", &self.headers) - } -} - -#[cfg(test)] -mod tests { - - use wasm_bindgen_test::*; - - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - - #[wasm_bindgen_test] - async fn test_multipart_js() { - use super::{Form, Part}; - use js_sys::Uint8Array; - use wasm_bindgen::JsValue; - use web_sys::{File, FormData}; - - let text_file_name = "test.txt"; - let text_file_type = "text/plain"; - let text_content = "TEST"; - let text_part = Part::text(text_content) - .file_name(text_file_name) - .mime_str(text_file_type) - .expect("invalid mime type"); - - let binary_file_name = "binary.bin"; - let binary_file_type = "application/octet-stream"; - let binary_content = vec![0u8, 42]; - let binary_part = Part::bytes(binary_content.clone()) - .file_name(binary_file_name) - .mime_str(binary_file_type) - .expect("invalid mime type"); - - let text_name = "text part"; - let binary_name = "binary part"; - let form = Form::new() - .part(text_name, text_part) - .part(binary_name, binary_part); - - let mut init = web_sys::RequestInit::new(); - init.method("POST"); - init.body(Some( - form.to_form_data() - .expect("could not convert to FormData") - .as_ref(), - )); - - let js_req = web_sys::Request::new_with_str_and_init("", &init) - .expect("could not create JS request"); - - let form_data_promise = js_req.form_data().expect("could not get form_data promise"); - - let form_data = crate::wasm::promise::(form_data_promise) - .await - .expect("could not get body as form data"); - - // check text part - let text_file = File::from(form_data.get(text_name)); - assert_eq!(text_file.name(), text_file_name); - assert_eq!(text_file.type_(), text_file_type); - - let text_promise = text_file.text(); - let text = crate::wasm::promise::(text_promise) - .await - .expect("could not get text body as text"); - assert_eq!( - text.as_string().expect("text is not a string"), - text_content - ); - - // check binary part - let binary_file = File::from(form_data.get(binary_name)); - assert_eq!(binary_file.name(), binary_file_name); - assert_eq!(binary_file.type_(), binary_file_type); - - let binary_array_buffer_promise = binary_file.array_buffer(); - let array_buffer = crate::wasm::promise::(binary_array_buffer_promise) - .await - .expect("could not get request body as array buffer"); - - let binary = Uint8Array::new(&array_buffer).to_vec(); - - assert_eq!(binary, binary_content); - } -} diff --git a/ergo-rest/src/reqwest/wasm/request.rs b/ergo-rest/src/reqwest/wasm/request.rs deleted file mode 100644 index f467b36ce..000000000 --- a/ergo-rest/src/reqwest/wasm/request.rs +++ /dev/null @@ -1,498 +0,0 @@ -use std::convert::TryFrom; -use std::fmt; -use std::time::Duration; - -use bytes::Bytes; -use http::{request::Parts, Method, Request as HttpRequest}; -use serde::Serialize; -#[cfg(feature = "json")] -use serde_json; -use url::Url; -use web_sys::RequestCredentials; - -use super::{Body, Client, Response}; -use crate::reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; - -/// A request which can be executed with `Client::execute()`. -pub struct Request { - method: Method, - url: Url, - headers: HeaderMap, - body: Option, - pub(super) cors: bool, - pub(super) credentials: Option, - timeout: Option, -} - -/// A builder to construct the properties of a `Request`. -pub struct RequestBuilder { - client: Client, - request: crate::reqwest::Result, -} - -impl Request { - /// Constructs a new request. - #[inline] - pub fn new(method: Method, url: Url) -> Self { - Request { - method, - url, - headers: HeaderMap::new(), - body: None, - cors: true, - credentials: None, - timeout: None, - } - } - - /// Get the method. - #[inline] - pub fn method(&self) -> &Method { - &self.method - } - - /// Get a mutable reference to the method. - #[inline] - pub fn method_mut(&mut self) -> &mut Method { - &mut self.method - } - - /// Get the url. - #[inline] - pub fn url(&self) -> &Url { - &self.url - } - - /// Get a mutable reference to the url. - #[inline] - pub fn url_mut(&mut self) -> &mut Url { - &mut self.url - } - - /// Get the headers. - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get a mutable reference to the headers. - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - /// Get the body. - #[inline] - pub fn body(&self) -> Option<&Body> { - self.body.as_ref() - } - - /// Get a mutable reference to the body. - #[inline] - pub fn body_mut(&mut self) -> &mut Option { - &mut self.body - } - - /// Get the timeout. - #[inline] - pub fn timeout(&self) -> Option<&Duration> { - self.timeout.as_ref() - } - - /// Get a mutable reference to the timeout. - #[inline] - pub fn timeout_mut(&mut self) -> &mut Option { - &mut self.timeout - } - - /// Attempts to clone the `Request`. - /// - /// None is returned if a body is which can not be cloned. - pub fn try_clone(&self) -> Option { - let body = match self.body.as_ref() { - Some(body) => Some(body.try_clone()?), - None => None, - }; - - Some(Self { - method: self.method.clone(), - url: self.url.clone(), - headers: self.headers.clone(), - body, - cors: self.cors, - credentials: self.credentials, - timeout: self.timeout, - }) - } -} - -impl RequestBuilder { - pub(super) fn new(client: Client, request: crate::reqwest::Result) -> RequestBuilder { - RequestBuilder { client, request } - } - - /// Modify the query string of the URL. - /// - /// Modifies the URL of this request, adding the parameters provided. - /// This method appends and does not overwrite. This means that it can - /// be called multiple times and that existing query parameters are not - /// overwritten if the same key is used. The key will simply show up - /// twice in the query string. - /// Calling `.query([("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`. - /// - /// # Note - /// This method does not support serializing a single key-value - /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such - /// as `.query(&[("key", "val")])`. It's also possible to serialize structs - /// and maps into a key-value pair. - /// - /// # Errors - /// This method will fail if the object you provide cannot be serialized - /// into a query string. - pub fn query(mut self, query: &T) -> RequestBuilder { - let mut error = None; - if let Ok(ref mut req) = self.request { - let url = req.url_mut(); - let mut pairs = url.query_pairs_mut(); - let serializer = serde_urlencoded::Serializer::new(&mut pairs); - - if let Err(err) = query.serialize(serializer) { - error = Some(crate::reqwest::error::builder(err)); - } - } - if let Ok(ref mut req) = self.request { - if let Some("") = req.url().query() { - req.url_mut().set_query(None); - } - } - if let Some(err) = error { - self.request = Err(err); - } - self - } - - /// Send a form body. - /// - /// Sets the body to the url encoded serialization of the passed value, - /// and also sets the `Content-Type: application/x-www-form-urlencoded` - /// header. - /// - /// # Errors - /// - /// This method fails if the passed value cannot be serialized into - /// url encoded format - pub fn form(mut self, form: &T) -> RequestBuilder { - let mut error = None; - if let Ok(ref mut req) = self.request { - match serde_urlencoded::to_string(form) { - Ok(body) => { - req.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/x-www-form-urlencoded"), - ); - *req.body_mut() = Some(body.into()); - } - Err(err) => error = Some(crate::reqwest::error::builder(err)), - } - } - if let Some(err) = error { - self.request = Err(err); - } - self - } - - #[cfg(feature = "json")] - #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - /// Set the request json - pub fn json(mut self, json: &T) -> RequestBuilder { - let mut error = None; - if let Ok(ref mut req) = self.request { - match serde_json::to_vec(json) { - Ok(body) => { - req.headers_mut() - .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - *req.body_mut() = Some(body.into()); - } - Err(err) => error = Some(crate::reqwest::error::builder(err)), - } - } - if let Some(err) = error { - self.request = Err(err); - } - self - } - - /// Enable HTTP bearer authentication. - pub fn bearer_auth(self, token: T) -> RequestBuilder - where - T: fmt::Display, - { - let header_value = format!("Bearer {}", token); - self.header(crate::reqwest::header::AUTHORIZATION, header_value) - } - - /// Enables a request timeout. - /// - /// The timeout is applied from when the request starts connecting until the - /// response body has finished. It affects only this request and overrides - /// the timeout configured using `ClientBuilder::timeout()`. - pub fn timeout(mut self, timeout: Duration) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - *req.timeout_mut() = Some(timeout); - } - self - } - - /// Set the request body. - pub fn body>(mut self, body: T) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - req.body = Some(body.into()); - } - self - } - - /// TODO - #[cfg(feature = "multipart")] - #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] - pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - *req.body_mut() = Some(Body::from_form(multipart)) - } - self - } - - /// Add a `Header` to this Request. - pub fn header(mut self, key: K, value: V) -> RequestBuilder - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - let mut error = None; - if let Ok(ref mut req) = self.request { - match >::try_from(key) { - Ok(key) => match >::try_from(value) { - Ok(value) => { - req.headers_mut().append(key, value); - } - Err(e) => error = Some(crate::reqwest::error::builder(e.into())), - }, - Err(e) => error = Some(crate::reqwest::error::builder(e.into())), - }; - } - if let Some(err) = error { - self.request = Err(err); - } - self - } - - /// Add a set of Headers to the existing ones on this Request. - /// - /// The headers will be merged in to any already set. - pub fn headers(mut self, headers: crate::reqwest::header::HeaderMap) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - crate::reqwest::util::replace_headers(req.headers_mut(), headers); - } - self - } - - /// Disable CORS on fetching the request. - /// - /// # WASM - /// - /// This option is only effective with WebAssembly target. - /// - /// The [request mode][mdn] will be set to 'no-cors'. - /// - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode - pub fn fetch_mode_no_cors(mut self) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - req.cors = false; - } - self - } - - /// Set fetch credentials to 'same-origin' - /// - /// # WASM - /// - /// This option is only effective with WebAssembly target. - /// - /// The [request credentials][mdn] will be set to 'same-origin'. - /// - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials - pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - req.credentials = Some(RequestCredentials::SameOrigin); - } - self - } - - /// Set fetch credentials to 'include' - /// - /// # WASM - /// - /// This option is only effective with WebAssembly target. - /// - /// The [request credentials][mdn] will be set to 'include'. - /// - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials - pub fn fetch_credentials_include(mut self) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - req.credentials = Some(RequestCredentials::Include); - } - self - } - - /// Set fetch credentials to 'omit' - /// - /// # WASM - /// - /// This option is only effective with WebAssembly target. - /// - /// The [request credentials][mdn] will be set to 'omit'. - /// - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials - pub fn fetch_credentials_omit(mut self) -> RequestBuilder { - if let Ok(ref mut req) = self.request { - req.credentials = Some(RequestCredentials::Omit); - } - self - } - - /// Build a `Request`, which can be inspected, modified and executed with - /// `Client::execute()`. - pub fn build(self) -> crate::reqwest::Result { - self.request - } - - /// Constructs the Request and sends it to the target URL, returning a - /// future Response. - /// - /// # Errors - /// - /// This method fails if there was an error while sending request. - /// - /// # Example - /// - /// ```no_run - /// # use reqwest::Error; - /// # - /// # async fn run() -> Result<(), Error> { - /// let response = reqwest::Client::new() - /// .get("https://hyper.rs") - /// .send() - /// .await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn send(self) -> crate::reqwest::Result { - let req = self.request?; - self.client.execute_request(req).await - } - - /// Attempt to clone the RequestBuilder. - /// - /// `None` is returned if the RequestBuilder can not be cloned. - /// - /// # Examples - /// - /// ```no_run - /// # use reqwest::Error; - /// # - /// # fn run() -> Result<(), Error> { - /// let client = reqwest::Client::new(); - /// let builder = client.post("http://httpbin.org/post") - /// .body("from a &str!"); - /// let clone = builder.try_clone(); - /// assert!(clone.is_some()); - /// # Ok(()) - /// # } - /// ``` - pub fn try_clone(&self) -> Option { - self.request - .as_ref() - .ok() - .and_then(|req| req.try_clone()) - .map(|req| RequestBuilder { - client: self.client.clone(), - request: Ok(req), - }) - } -} - -impl fmt::Debug for Request { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_request_fields(&mut f.debug_struct("Request"), self).finish() - } -} - -impl fmt::Debug for RequestBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut builder = f.debug_struct("RequestBuilder"); - match self.request { - Ok(ref req) => fmt_request_fields(&mut builder, req).finish(), - Err(ref err) => builder.field("error", err).finish(), - } - } -} - -fn fmt_request_fields<'a, 'b>( - f: &'a mut fmt::DebugStruct<'a, 'b>, - req: &Request, -) -> &'a mut fmt::DebugStruct<'a, 'b> { - f.field("method", &req.method) - .field("url", &req.url) - .field("headers", &req.headers) -} - -impl TryFrom> for Request -where - T: Into, -{ - type Error = crate::reqwest::Error; - - fn try_from(req: HttpRequest) -> crate::reqwest::Result { - let (parts, body) = req.into_parts(); - let Parts { - method, - uri, - headers, - .. - } = parts; - let url = Url::parse(&uri.to_string()).map_err(crate::reqwest::error::builder)?; - Ok(Request { - method, - url, - headers, - body: Some(body.into()), - cors: true, - credentials: None, - timeout: None, - }) - } -} - -impl TryFrom for HttpRequest { - type Error = crate::reqwest::Error; - - fn try_from(req: Request) -> crate::reqwest::Result { - let Request { - method, - url, - headers, - body, - .. - } = req; - - let mut req = HttpRequest::builder() - .method(method) - .uri(url.as_str()) - .body(body.unwrap_or_else(|| Body::from(Bytes::default()))) - .map_err(crate::reqwest::error::builder)?; - - *req.headers_mut() = headers; - Ok(req) - } -} diff --git a/ergo-rest/src/reqwest/wasm/response.rs b/ergo-rest/src/reqwest/wasm/response.rs deleted file mode 100644 index f5b8c4015..000000000 --- a/ergo-rest/src/reqwest/wasm/response.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::fmt; - -use bytes::Bytes; -use http::{HeaderMap, StatusCode}; -use js_sys::Uint8Array; -use url::Url; - -#[cfg(feature = "json")] -use serde::de::DeserializeOwned; - -/// A Response to a submitted `Request`. -pub struct Response { - http: http::Response, - // Boxed to save space (11 words to 1 word), and it's not accessed - // frequently internally. - url: Box, -} - -impl Response { - pub(super) fn new(res: http::Response, url: Url) -> Response { - Response { - http: res, - url: Box::new(url), - } - } - - /// Get the `StatusCode` of this `Response`. - #[inline] - pub fn status(&self) -> StatusCode { - self.http.status() - } - - /// Get the `Headers` of this `Response`. - #[inline] - pub fn headers(&self) -> &HeaderMap { - self.http.headers() - } - - /// Get a mutable reference to the `Headers` of this `Response`. - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - self.http.headers_mut() - } - - /// Get the content-length of this response, if known. - /// - /// Reasons it may not be known: - /// - /// - The server didn't send a `content-length` header. - /// - The response is compressed and automatically decoded (thus changing - /// the actual decoded length). - pub fn content_length(&self) -> Option { - self.headers() - .get(http::header::CONTENT_LENGTH)? - .to_str() - .ok()? - .parse() - .ok() - } - - /// Get the final `Url` of this `Response`. - #[inline] - pub fn url(&self) -> &Url { - &self.url - } - - /* It might not be possible to detect this in JS? - /// Get the HTTP `Version` of this `Response`. - #[inline] - pub fn version(&self) -> Version { - self.http.version() - } - */ - - /// Try to deserialize the response body as JSON. - #[cfg(feature = "json")] - #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - pub async fn json(self) -> crate::reqwest::Result { - let full = self.bytes().await?; - - serde_json::from_slice(&full).map_err(crate::reqwest::error::decode) - } - - /// Get the response text. - pub async fn text(self) -> crate::reqwest::Result { - let p = self - .http - .body() - .text() - .map_err(crate::reqwest::error::wasm) - .map_err(crate::reqwest::error::decode)?; - let js_val = super::promise::(p) - .await - .map_err(crate::reqwest::error::decode)?; - if let Some(s) = js_val.as_string() { - Ok(s) - } else { - Err(crate::reqwest::error::decode("response.text isn't string")) - } - } - - /// Get the response as bytes - pub async fn bytes(self) -> crate::reqwest::Result { - let p = self - .http - .body() - .array_buffer() - .map_err(crate::reqwest::error::wasm) - .map_err(crate::reqwest::error::decode)?; - - let buf_js = super::promise::(p) - .await - .map_err(crate::reqwest::error::decode)?; - - let buffer = Uint8Array::new(&buf_js); - let mut bytes = vec![0; buffer.length() as usize]; - buffer.copy_to(&mut bytes); - Ok(bytes.into()) - } - - // util methods - - /// Turn a response into an error if the server returned an error. - pub fn error_for_status(self) -> crate::reqwest::Result { - let status = self.status(); - if status.is_client_error() || status.is_server_error() { - Err(crate::reqwest::error::status_code(*self.url, status)) - } else { - Ok(self) - } - } - - /// Turn a reference to a response into an error if the server returned an error. - pub fn error_for_status_ref(&self) -> crate::reqwest::Result<&Self> { - let status = self.status(); - if status.is_client_error() || status.is_server_error() { - Err(crate::reqwest::error::status_code(*self.url.clone(), status)) - } else { - Ok(self) - } - } -} - -impl fmt::Debug for Response { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Response") - //.field("url", self.url()) - .field("status", &self.status()) - .field("headers", self.headers()) - .finish() - } -} diff --git a/ergo-rest/src/wasm_timer.rs b/ergo-rest/src/wasm_timer.rs deleted file mode 100644 index bc0ab054e..000000000 --- a/ergo-rest/src/wasm_timer.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2019 Pierre Krieger -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -#![allow(clippy::all)] -pub use timer::*; - -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] -pub(crate) use std::time::Instant; -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -use wasm::*; - -mod timer; -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -mod wasm; diff --git a/ergo-rest/src/wasm_timer/LICENSE b/ergo-rest/src/wasm_timer/LICENSE deleted file mode 100644 index 8acf927bb..000000000 --- a/ergo-rest/src/wasm_timer/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2019 Pierre Krieger -Copyright (c) 2019 Tokio Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ergo-rest/src/wasm_timer/timer.rs b/ergo-rest/src/wasm_timer/timer.rs deleted file mode 100644 index 03e6e005b..000000000 --- a/ergo-rest/src/wasm_timer/timer.rs +++ /dev/null @@ -1,633 +0,0 @@ -// The `timer` module is a copy-paste from the code of `futures-timer`, but -// adjusted for WASM. -// -// Copyright (c) 2014 Alex Crichton -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// -// Apache License -// Version 2.0, January 2004 -// http://www.apache.org/licenses/ -// -// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -// -// 1. Definitions. -// -// "License" shall mean the terms and conditions for use, reproduction, -// and distribution as defined by Sections 1 through 9 of this document. -// -// "Licensor" shall mean the copyright owner or entity authorized by -// the copyright owner that is granting the License. -// -// "Legal Entity" shall mean the union of the acting entity and all -// other entities that control, are controlled by, or are under common -// control with that entity. For the purposes of this definition, -// "control" means (i) the power, direct or indirect, to cause the -// direction or management of such entity, whether by contract or -// otherwise, or (ii) ownership of fifty percent (50%) or more of the -// outstanding shares, or (iii) beneficial ownership of such entity. -// -// "You" (or "Your") shall mean an individual or Legal Entity -// exercising permissions granted by this License. -// -// "Source" form shall mean the preferred form for making modifications, -// including but not limited to software source code, documentation -// source, and configuration files. -// -// "Object" form shall mean any form resulting from mechanical -// transformation or translation of a Source form, including but -// not limited to compiled object code, generated documentation, -// and conversions to other media types. -// -// "Work" shall mean the work of authorship, whether in Source or -// Object form, made available under the License, as indicated by a -// copyright notice that is included in or attached to the work -// (an example is provided in the Appendix below). -// -// "Derivative Works" shall mean any work, whether in Source or Object -// form, that is based on (or derived from) the Work and for which the -// editorial revisions, annotations, elaborations, or other modifications -// represent, as a whole, an original work of authorship. For the purposes -// of this License, Derivative Works shall not include works that remain -// separable from, or merely link (or bind by name) to the interfaces of, -// the Work and Derivative Works thereof. -// -// "Contribution" shall mean any work of authorship, including -// the original version of the Work and any modifications or additions -// to that Work or Derivative Works thereof, that is intentionally -// submitted to Licensor for inclusion in the Work by the copyright owner -// or by an individual or Legal Entity authorized to submit on behalf of -// the copyright owner. For the purposes of this definition, "submitted" -// means any form of electronic, verbal, or written communication sent -// to the Licensor or its representatives, including but not limited to -// communication on electronic mailing lists, source code control systems, -// and issue tracking systems that are managed by, or on behalf of, the -// Licensor for the purpose of discussing and improving the Work, but -// excluding communication that is conspicuously marked or otherwise -// designated in writing by the copyright owner as "Not a Contribution." -// -// "Contributor" shall mean Licensor and any individual or Legal Entity -// on behalf of whom a Contribution has been received by Licensor and -// subsequently incorporated within the Work. -// -// 2. Grant of Copyright License. Subject to the terms and conditions of -// this License, each Contributor hereby grants to You a perpetual, -// worldwide, non-exclusive, no-charge, royalty-free, irrevocable -// copyright license to reproduce, prepare Derivative Works of, -// publicly display, publicly perform, sublicense, and distribute the -// Work and such Derivative Works in Source or Object form. -// -// 3. Grant of Patent License. Subject to the terms and conditions of -// this License, each Contributor hereby grants to You a perpetual, -// worldwide, non-exclusive, no-charge, royalty-free, irrevocable -// (except as stated in this section) patent license to make, have made, -// use, offer to sell, sell, import, and otherwise transfer the Work, -// where such license applies only to those patent claims licensable -// by such Contributor that are necessarily infringed by their -// Contribution(s) alone or by combination of their Contribution(s) -// with the Work to which such Contribution(s) was submitted. If You -// institute patent litigation against any entity (including a -// cross-claim or counterclaim in a lawsuit) alleging that the Work -// or a Contribution incorporated within the Work constitutes direct -// or contributory patent infringement, then any patent licenses -// granted to You under this License for that Work shall terminate -// as of the date such litigation is filed. -// -// 4. Redistribution. You may reproduce and distribute copies of the -// Work or Derivative Works thereof in any medium, with or without -// modifications, and in Source or Object form, provided that You -// meet the following conditions: -// -// (a) You must give any other recipients of the Work or -// Derivative Works a copy of this License; and -// -// (b) You must cause any modified files to carry prominent notices -// stating that You changed the files; and -// -// (c) You must retain, in the Source form of any Derivative Works -// that You distribute, all copyright, patent, trademark, and -// attribution notices from the Source form of the Work, -// excluding those notices that do not pertain to any part of -// the Derivative Works; and -// -// (d) If the Work includes a "NOTICE" text file as part of its -// distribution, then any Derivative Works that You distribute must -// include a readable copy of the attribution notices contained -// within such NOTICE file, excluding those notices that do not -// pertain to any part of the Derivative Works, in at least one -// of the following places: within a NOTICE text file distributed -// as part of the Derivative Works; within the Source form or -// documentation, if provided along with the Derivative Works; or, -// within a display generated by the Derivative Works, if and -// wherever such third-party notices normally appear. The contents -// of the NOTICE file are for informational purposes only and -// do not modify the License. You may add Your own attribution -// notices within Derivative Works that You distribute, alongside -// or as an addendum to the NOTICE text from the Work, provided -// that such additional attribution notices cannot be construed -// as modifying the License. -// -// You may add Your own copyright statement to Your modifications and -// may provide additional or different license terms and conditions -// for use, reproduction, or distribution of Your modifications, or -// for any such Derivative Works as a whole, provided Your use, -// reproduction, and distribution of the Work otherwise complies with -// the conditions stated in this License. -// -// 5. Submission of Contributions. Unless You explicitly state otherwise, -// any Contribution intentionally submitted for inclusion in the Work -// by You to the Licensor shall be under the terms and conditions of -// this License, without any additional terms or conditions. -// Notwithstanding the above, nothing herein shall supersede or modify -// the terms of any separate license agreement you may have executed -// with Licensor regarding such Contributions. -// -// 6. Trademarks. This License does not grant permission to use the trade -// names, trademarks, service marks, or product names of the Licensor, -// except as required for reasonable and customary use in describing the -// origin of the Work and reproducing the content of the NOTICE file. -// -// 7. Disclaimer of Warranty. Unless required by applicable law or -// agreed to in writing, Licensor provides the Work (and each -// Contributor provides its Contributions) on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied, including, without limitation, any warranties or conditions -// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -// PARTICULAR PURPOSE. You are solely responsible for determining the -// appropriateness of using or redistributing the Work and assume any -// risks associated with Your exercise of permissions under this License. -// -// 8. Limitation of Liability. In no event and under no legal theory, -// whether in tort (including negligence), contract, or otherwise, -// unless required by applicable law (such as deliberate and grossly -// negligent acts) or agreed to in writing, shall any Contributor be -// liable to You for damages, including any direct, indirect, special, -// incidental, or consequential damages of any character arising as a -// result of this License or out of the use or inability to use the -// Work (including but not limited to damages for loss of goodwill, -// work stoppage, computer failure or malfunction, or any and all -// other commercial damages or losses), even if such Contributor -// has been advised of the possibility of such damages. -// -// 9. Accepting Warranty or Additional Liability. While redistributing -// the Work or Derivative Works thereof, You may choose to offer, -// and charge a fee for, acceptance of support, warranty, indemnity, -// or other liability obligations and/or rights consistent with this -// License. However, in accepting such obligations, You may act only -// on Your own behalf and on Your sole responsibility, not on behalf -// of any other Contributor, and only if You agree to indemnify, -// defend, and hold each Contributor harmless for any liability -// incurred by, or claims asserted against, such Contributor by reason -// of your accepting any such warranty or additional liability. -// -// END OF TERMS AND CONDITIONS -// -// APPENDIX: How to apply the Apache License to your work. -// -// To apply the Apache License to your work, attach the following -// boilerplate notice, with the fields enclosed by brackets "[]" -// replaced with your own identifying information. (Don't include -// the brackets!) The text should be enclosed in the appropriate -// comment syntax for the file format. We also recommend that a -// file or class name and description of purpose be included on the -// same "printed page" as the copyright notice for easier -// identification within third-party archives. -// -// Copyright [yyyy] [name of copyright owner] -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::wasm_timer::Instant; -use std::cmp::Ordering; -use std::fmt; -use std::mem; -use std::pin::Pin; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering::SeqCst; -use std::sync::{Arc, Mutex, Weak}; -use std::task::{Context, Poll}; - -use futures::prelude::*; -use futures::task::AtomicWaker; - -use arc_list::{ArcList, Node}; -use heap::{Heap, Slot}; - -mod arc_list; -mod global; -mod heap; - -pub mod ext; - -/// A "timer heap" used to power separately owned instances of `Delay` and -/// `Interval`. -/// -/// This timer is implemented as a priority queued-based heap. Each `Timer` -/// contains a few primary methods which which to drive it: -/// -/// * `next_wake` indicates how long the ambient system needs to sleep until it -/// invokes further processing on a `Timer` -/// * `advance_to` is what actually fires timers on the `Timer`, and should be -/// called essentially every iteration of the event loop, or when the time -/// specified by `next_wake` has elapsed. -/// * The `Future` implementation for `Timer` is used to process incoming timer -/// updates and requests. This is used to schedule new timeouts, update -/// existing ones, or delete existing timeouts. The `Future` implementation -/// will never resolve, but it'll schedule notifications of when to wake up -/// and process more messages. -/// -/// Note that if you're using this crate you probably don't need to use a -/// `Timer` as there is a global one already available for you run on a helper -/// thread. If this isn't desirable, though, then the -/// `TimerHandle::set_fallback` method can be used instead! -pub struct Timer { - inner: Arc, - timer_heap: Heap, -} - -/// A handle to a `Timer` which is used to create instances of a `Delay`. -#[derive(Clone)] -pub struct TimerHandle { - inner: Weak, -} - -mod delay; -mod interval; -pub use self::delay::Delay; - -struct Inner { - /// List of updates the `Timer` needs to process - list: ArcList, - - /// The blocked `Timer` task to receive notifications to the `list` above. - waker: AtomicWaker, -} - -/// Shared state between the `Timer` and a `Delay`. -struct ScheduledTimer { - waker: AtomicWaker, - - // The lowest bit here is whether the timer has fired or not, the second - // lowest bit is whether the timer has been invalidated, and all the other - // bits are the "generation" of the timer which is reset during the `reset` - // function. Only timers for a matching generation are fired. - state: AtomicUsize, - - inner: Weak, - at: Mutex>, - - // TODO: this is only accessed by the timer thread, should have a more - // lightweight protection than a `Mutex` - slot: Mutex>, -} - -/// Entries in the timer heap, sorted by the instant they're firing at and then -/// also containing some payload data. -struct HeapTimer { - at: Instant, - gen: usize, - node: Arc>, -} - -impl Timer { - /// Creates a new timer heap ready to create new timers. - pub fn new() -> Timer { - Timer { - inner: Arc::new(Inner { - list: ArcList::new(), - waker: AtomicWaker::new(), - }), - timer_heap: Heap::new(), - } - } - - /// Returns a handle to this timer heap, used to create new timeouts. - pub fn handle(&self) -> TimerHandle { - TimerHandle { - inner: Arc::downgrade(&self.inner), - } - } - - /// Returns the time at which this timer next needs to be invoked with - /// `advance_to`. - /// - /// Event loops or threads typically want to sleep until the specified - /// instant. - pub fn next_event(&self) -> Option { - self.timer_heap.peek().map(|t| t.at) - } - - /// Proces any timers which are supposed to fire at or before the current - /// instant. - /// - /// This method is equivalent to `self.advance_to(Instant::now())`. - pub fn advance(&mut self) { - self.advance_to(Instant::now()) - } - - /// Proces any timers which are supposed to fire before `now` specified. - /// - /// This method should be called on `Timer` periodically to advance the - /// internal state and process any pending timers which need to fire. - pub fn advance_to(&mut self, now: Instant) { - loop { - match self.timer_heap.peek() { - Some(head) if head.at <= now => {} - Some(_) => break, - None => break, - }; - - // Flag the timer as fired and then notify its task, if any, that's - // blocked. - let heap_timer = { - #[allow(clippy::unwrap_used)] - self.timer_heap.pop().unwrap() - }; - #[allow(clippy::unwrap_used)] - { - *heap_timer.node.slot.lock().unwrap() = None; - } - let bits = heap_timer.gen << 2; - match heap_timer - .node - .state - .compare_exchange(bits, bits | 0b01, SeqCst, SeqCst) - { - Ok(_) => heap_timer.node.waker.wake(), - Err(_b) => {} - } - } - } - - /// Either updates the timer at slot `idx` to fire at `at`, or adds a new - /// timer at `idx` and sets it to fire at `at`. - fn update_or_add(&mut self, at: Instant, node: Arc>) { - // TODO: avoid remove + push and instead just do one sift of the heap? - // In theory we could update it in place and then do the percolation - // as necessary - let gen = node.state.load(SeqCst) >> 2; - #[allow(clippy::unwrap_used)] - let mut slot = node.slot.lock().unwrap(); - if let Some(heap_slot) = slot.take() { - self.timer_heap.remove(heap_slot); - } - *slot = Some(self.timer_heap.push(HeapTimer { - at: at, - gen: gen, - node: node.clone(), - })); - } - - fn remove(&mut self, node: Arc>) { - // If this `idx` is still around and it's still got a registered timer, - // then we jettison it form the timer heap. - #[allow(clippy::unwrap_used)] - let mut slot = node.slot.lock().unwrap(); - let heap_slot = match slot.take() { - Some(slot) => slot, - None => return, - }; - self.timer_heap.remove(heap_slot); - } - - fn invalidate(&mut self, node: Arc>) { - node.state.fetch_or(0b10, SeqCst); - node.waker.wake(); - } -} - -impl Future for Timer { - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Pin::new(&mut self.inner).waker.register(cx.waker()); - let mut list = self.inner.list.take(); - while let Some(node) = list.pop() { - #[allow(clippy::unwrap_used)] - let at = *node.at.lock().unwrap(); - match at { - Some(at) => self.update_or_add(at, node), - None => self.remove(node), - } - } - Poll::Pending - } -} - -impl Drop for Timer { - fn drop(&mut self) { - // Seal off our list to prevent any more updates from getting pushed on. - // Any timer which sees an error from the push will immediately become - // inert. - let mut list = self.inner.list.take_and_seal(); - - // Now that we'll never receive another timer, drain the list of all - // updates and also drain our heap of all active timers, invalidating - // everything. - while let Some(t) = list.pop() { - self.invalidate(t); - } - while let Some(t) = self.timer_heap.pop() { - self.invalidate(t.node); - } - } -} - -impl fmt::Debug for Timer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_struct("Timer").field("heap", &"...").finish() - } -} - -impl PartialEq for HeapTimer { - fn eq(&self, other: &HeapTimer) -> bool { - self.at == other.at - } -} - -impl Eq for HeapTimer {} - -impl PartialOrd for HeapTimer { - fn partial_cmp(&self, other: &HeapTimer) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for HeapTimer { - fn cmp(&self, other: &HeapTimer) -> Ordering { - self.at.cmp(&other.at) - } -} - -static HANDLE_FALLBACK: AtomicUsize = AtomicUsize::new(0); - -/// Error returned from `TimerHandle::set_fallback`. -#[derive(Clone, Debug)] -pub struct SetDefaultError(()); - -impl TimerHandle { - /// Configures this timer handle to be the one returned by - /// `TimerHandle::default`. - /// - /// By default a global thread is initialized on the first call to - /// `TimerHandle::default`. This first call can happen transitively through - /// `Delay::new`. If, however, that hasn't happened yet then the global - /// default timer handle can be configured through this method. - /// - /// This method can be used to prevent the global helper thread from - /// spawning. If this method is successful then the global helper thread - /// will never get spun up. - /// - /// On success this timer handle will have installed itself globally to be - /// used as the return value for `TimerHandle::default` unless otherwise - /// specified. - /// - /// # Errors - /// - /// If another thread has already called `set_as_global_fallback` or this - /// thread otherwise loses a race to call this method then it will fail - /// returning an error. Once a call to `set_as_global_fallback` is - /// successful then no future calls may succeed. - pub fn set_as_global_fallback(self) -> Result<(), SetDefaultError> { - unsafe { - let val = self.into_usize(); - match HANDLE_FALLBACK.compare_exchange(0, val, SeqCst, SeqCst) { - Ok(_) => Ok(()), - Err(_) => { - drop(TimerHandle::from_usize(val)); - Err(SetDefaultError(())) - } - } - } - } - - fn into_usize(self) -> usize { - unsafe { mem::transmute::, usize>(self.inner) } - } - - unsafe fn from_usize(val: usize) -> TimerHandle { - let inner = mem::transmute::>(val); - TimerHandle { inner } - } -} - -impl Default for TimerHandle { - #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] - fn default() -> TimerHandle { - let mut fallback = HANDLE_FALLBACK.load(SeqCst); - - // If the fallback hasn't been previously initialized then let's spin - // up a helper thread and try to initialize with that. If we can't - // actually create a helper thread then we'll just return a "defunkt" - // handle which will return errors when timer objects are attempted to - // be associated. - if fallback == 0 { - let helper = match global::HelperThread::new() { - Ok(helper) => helper, - Err(_) => return TimerHandle { inner: Weak::new() }, - }; - - // If we successfully set ourselves as the actual fallback then we - // want to `forget` the helper thread to ensure that it persists - // globally. If we fail to set ourselves as the fallback that means - // that someone was racing with this call to - // `TimerHandle::default`. They ended up winning so we'll destroy - // our helper thread (which shuts down the thread) and reload the - // fallback. - if helper.handle().set_as_global_fallback().is_ok() { - let ret = helper.handle(); - helper.forget(); - return ret; - } - fallback = HANDLE_FALLBACK.load(SeqCst); - } - - // At this point our fallback handle global was configured so we use - // its value to reify a handle, clone it, and then forget our reified - // handle as we don't actually have an owning reference to it. - assert!(fallback != 0); - unsafe { - let handle = TimerHandle::from_usize(fallback); - let ret = handle.clone(); - handle.into_usize(); - return ret; - } - } - - #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] - fn default() -> TimerHandle { - let mut fallback = HANDLE_FALLBACK.load(SeqCst); - - // If the fallback hasn't been previously initialized then let's spin - // up a helper thread and try to initialize with that. If we can't - // actually create a helper thread then we'll just return a "defunkt" - // handle which will return errors when timer objects are attempted to - // be associated. - if fallback == 0 { - let handle = global::run(); - - // If we successfully set ourselves as the actual fallback then we - // want to `forget` the helper thread to ensure that it persists - // globally. If we fail to set ourselves as the fallback that means - // that someone was racing with this call to - // `TimerHandle::default`. They ended up winning so we'll destroy - // our helper thread (which shuts down the thread) and reload the - // fallback. - if handle.clone().set_as_global_fallback().is_ok() { - return handle; - } - fallback = HANDLE_FALLBACK.load(SeqCst); - } - - // At this point our fallback handle global was configured so we use - // its value to reify a handle, clone it, and then forget our reified - // handle as we don't actually have an owning reference to it. - assert!(fallback != 0); - unsafe { - let handle = TimerHandle::from_usize(fallback); - let ret = handle.clone(); - drop(handle.into_usize()); - return ret; - } - } -} - -impl fmt::Debug for TimerHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_struct("TimerHandle") - .field("inner", &"...") - .finish() - } -} diff --git a/ergo-rest/src/wasm_timer/timer/arc_list.rs b/ergo-rest/src/wasm_timer/timer/arc_list.rs deleted file mode 100644 index 0b51b9e5d..000000000 --- a/ergo-rest/src/wasm_timer/timer/arc_list.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! An atomically managed intrusive linked list of `Arc` nodes - -use std::marker; -use std::ops::Deref; -use std::sync::atomic::Ordering::SeqCst; -use std::sync::atomic::{AtomicBool, AtomicUsize}; -use std::sync::Arc; - -pub struct ArcList { - list: AtomicUsize, - _marker: marker::PhantomData, -} - -impl ArcList { - pub fn new() -> ArcList { - ArcList { - list: AtomicUsize::new(0), - _marker: marker::PhantomData, - } - } - - /// Pushes the `data` provided onto this list if it's not already enqueued - /// in this list. - /// - /// If `data` is already enqueued in this list then this is a noop, - /// otherwise, the `data` here is pushed on the end of the list. - pub fn push(&self, data: &Arc>) -> Result<(), ()> { - if data.enqueued.swap(true, SeqCst) { - // note that even if our list is sealed off then the other end is - // still guaranteed to see us because we were previously enqueued. - return Ok(()); - } - let mut head = self.list.load(SeqCst); - let node = Arc::into_raw(data.clone()) as usize; - loop { - // If we've been sealed off, abort and return an error - if head == 1 { - unsafe { - drop(Arc::from_raw(node as *mut Node)); - } - return Err(()); - } - - // Otherwise attempt to push this node - data.next.store(head, SeqCst); - match self.list.compare_exchange(head, node, SeqCst, SeqCst) { - Ok(_) => break Ok(()), - Err(new_head) => head = new_head, - } - } - } - - /// Atomically empties this list, returning a new owned copy which can be - /// used to iterate over the entries. - pub fn take(&self) -> ArcList { - let mut list = self.list.load(SeqCst); - loop { - if list == 1 { - break; - } - match self.list.compare_exchange(list, 0, SeqCst, SeqCst) { - Ok(_) => break, - Err(l) => list = l, - } - } - ArcList { - list: AtomicUsize::new(list), - _marker: marker::PhantomData, - } - } - - /// Atomically empties this list and prevents further successful calls to - /// `push`. - pub fn take_and_seal(&self) -> ArcList { - ArcList { - list: AtomicUsize::new(self.list.swap(1, SeqCst)), - _marker: marker::PhantomData, - } - } - - /// Removes the head of the list of nodes, returning `None` if this is an - /// empty list. - pub fn pop(&mut self) -> Option>> { - let head = *self.list.get_mut(); - if head == 0 || head == 1 { - return None; - } - let head = unsafe { Arc::from_raw(head as *const Node) }; - *self.list.get_mut() = head.next.load(SeqCst); - // At this point, the node is out of the list, so store `false` so we - // can enqueue it again and see further changes. - assert!(head.enqueued.swap(false, SeqCst)); - Some(head) - } -} - -impl Drop for ArcList { - fn drop(&mut self) { - while let Some(_) = self.pop() { - // ... - } - } -} - -pub struct Node { - next: AtomicUsize, - enqueued: AtomicBool, - data: T, -} - -impl Node { - pub fn new(data: T) -> Node { - Node { - next: AtomicUsize::new(0), - enqueued: AtomicBool::new(false), - data: data, - } - } -} - -impl Deref for Node { - type Target = T; - - fn deref(&self) -> &T { - &self.data - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod tests { - use super::*; - - #[test] - fn smoke() { - let a = ArcList::new(); - let n = Arc::new(Node::new(1)); - assert!(a.push(&n).is_ok()); - - let mut l = a.take(); - assert_eq!(**l.pop().unwrap(), 1); - assert!(l.pop().is_none()); - } - - #[test] - fn seal() { - let a = ArcList::new(); - let n = Arc::new(Node::new(1)); - let mut l = a.take_and_seal(); - assert!(l.pop().is_none()); - assert!(a.push(&n).is_err()); - - assert!(a.take().pop().is_none()); - assert!(a.take_and_seal().pop().is_none()); - } -} diff --git a/ergo-rest/src/wasm_timer/timer/delay.rs b/ergo-rest/src/wasm_timer/timer/delay.rs deleted file mode 100644 index 60a53aee2..000000000 --- a/ergo-rest/src/wasm_timer/timer/delay.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! Support for creating futures that represent timeouts. -//! -//! This module contains the `Delay` type which is a future that will resolve -//! at a particular point in the future. - -use std::fmt; -use std::future::Future; -use std::io; -use std::pin::Pin; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering::SeqCst; -use std::sync::{Arc, Mutex}; -use std::task::{Context, Poll}; -use std::time::Duration; - -use futures::task::AtomicWaker; - -use super::arc_list::Node; -use super::Instant; -use super::{ScheduledTimer, TimerHandle}; - -/// A future representing the notification that an elapsed duration has -/// occurred. -/// -/// This is created through the `Delay::new` or `Delay::new_at` methods -/// indicating when the future should fire at. Note that these futures are not -/// intended for high resolution timers, but rather they will likely fire some -/// granularity after the exact instant that they're otherwise indicated to -/// fire at. -pub struct Delay { - state: Option>>, - when: Instant, -} - -impl Delay { - /// Creates a new future which will fire at `dur` time into the future. - /// - /// The returned object will be bound to the default timer for this thread. - /// The default timer will be spun up in a helper thread on first use. - #[inline] - pub fn new(dur: Duration) -> Delay { - Delay::new_at(Instant::now() + dur) - } - - /// Creates a new future which will fire at the time specified by `at`. - /// - /// The returned object will be bound to the default timer for this thread. - /// The default timer will be spun up in a helper thread on first use. - #[inline] - pub fn new_at(at: Instant) -> Delay { - Delay::new_handle(at, Default::default()) - } - - /// Creates a new future which will fire at the time specified by `at`. - /// - /// The returned instance of `Delay` will be bound to the timer specified by - /// the `handle` argument. - pub fn new_handle(at: Instant, handle: TimerHandle) -> Delay { - let inner = match handle.inner.upgrade() { - Some(i) => i, - None => { - return Delay { - state: None, - when: at, - } - } - }; - let state = Arc::new(Node::new(ScheduledTimer { - at: Mutex::new(Some(at)), - state: AtomicUsize::new(0), - waker: AtomicWaker::new(), - inner: handle.inner, - slot: Mutex::new(None), - })); - - // If we fail to actually push our node then we've become an inert - // timer, meaning that we'll want to immediately return an error from - // `poll`. - if inner.list.push(&state).is_err() { - return Delay { - state: None, - when: at, - }; - } - - inner.waker.wake(); - Delay { - state: Some(state), - when: at, - } - } - - /// Resets this timeout to an new timeout which will fire at the time - /// specified by `dur`. - /// - /// This is equivalent to calling `reset_at` with `Instant::now() + dur` - #[inline] - pub fn reset(&mut self, dur: Duration) { - self.reset_at(Instant::now() + dur) - } - - /// Resets this timeout to an new timeout which will fire at the time - /// specified by `at`. - /// - /// This method is usable even of this instance of `Delay` has "already - /// fired". That is, if this future has resolved, calling this method means - /// that the future will still re-resolve at the specified instant. - /// - /// If `at` is in the past then this future will immediately be resolved - /// (when `poll` is called). - /// - /// Note that if any task is currently blocked on this future then that task - /// will be dropped. It is required to call `poll` again after this method - /// has been called to ensure tha ta task is blocked on this future. - #[inline] - pub fn reset_at(&mut self, at: Instant) { - self.when = at; - if self._reset(at).is_err() { - self.state = None - } - } - - fn _reset(&mut self, at: Instant) -> Result<(), ()> { - let state = match self.state { - Some(ref state) => state, - None => return Err(()), - }; - if let Some(timeouts) = state.inner.upgrade() { - let mut bits = state.state.load(SeqCst); - loop { - // If we've been invalidated, cancel this reset - if bits & 0b10 != 0 { - return Err(()); - } - let new = bits.wrapping_add(0b100) & !0b11; - match state.state.compare_exchange(bits, new, SeqCst, SeqCst) { - Ok(_) => break, - Err(s) => bits = s, - } - } - #[allow(clippy::unwrap_used)] - { - *state.at.lock().unwrap() = Some(at); - } - // If we fail to push our node then we've become an inert timer, so - // we'll want to clear our `state` field accordingly - timeouts.list.push(state)?; - timeouts.waker.wake(); - } - - Ok(()) - } -} - -#[inline] -pub fn fires_at(timeout: &Delay) -> Instant { - timeout.when -} - -impl Future for Delay { - type Output = io::Result<()>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let state = match self.state { - Some(ref state) => state, - None => { - let err = Err(io::Error::new(io::ErrorKind::Other, "timer has gone away")); - return Poll::Ready(err); - } - }; - - if state.state.load(SeqCst) & 1 != 0 { - return Poll::Ready(Ok(())); - } - - state.waker.register(&cx.waker()); - - // Now that we've registered, do the full check of our own internal - // state. If we've fired the first bit is set, and if we've been - // invalidated the second bit is set. - match state.state.load(SeqCst) { - n if n & 0b01 != 0 => Poll::Ready(Ok(())), - n if n & 0b10 != 0 => Poll::Ready(Err(io::Error::new( - io::ErrorKind::Other, - "timer has gone away", - ))), - _ => Poll::Pending, - } - } -} - -impl Drop for Delay { - fn drop(&mut self) { - let state = match self.state { - Some(ref s) => s, - None => return, - }; - if let Some(timeouts) = state.inner.upgrade() { - #[allow(clippy::unwrap_used)] - { - *state.at.lock().unwrap() = None; - } - if timeouts.list.push(state).is_ok() { - timeouts.waker.wake(); - } - } - } -} - -impl fmt::Debug for Delay { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_struct("Delay").field("when", &self.when).finish() - } -} diff --git a/ergo-rest/src/wasm_timer/timer/ext.rs b/ergo-rest/src/wasm_timer/timer/ext.rs deleted file mode 100644 index 86c8cbd39..000000000 --- a/ergo-rest/src/wasm_timer/timer/ext.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! Extension traits for the standard `Stream` and `Future` traits. - -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; - -use futures::prelude::*; -use pin_utils::unsafe_pinned; - -use super::{delay::Delay, Instant}; - -/// An extension trait for futures which provides convenient accessors for -/// timing out execution and such. -pub trait TryFutureExt: TryFuture + Sized { - /// Creates a new future which will take at most `dur` time to resolve from - /// the point at which this method is called. - /// - /// This combinator creates a new future which wraps the receiving future - /// in a timeout. The future returned will resolve in at most `dur` time - /// specified (relative to when this function is called). - /// - /// If the future completes before `dur` elapses then the future will - /// resolve with that item. Otherwise the future will resolve to an error - /// once `dur` has elapsed. - fn timeout(self, dur: Duration) -> Timeout - where - Self::Error: From, - { - Timeout { - timeout: Delay::new(dur), - future: self, - } - } - - /// Creates a new future which will resolve no later than `at` specified. - /// - /// This method is otherwise equivalent to the `timeout` method except that - /// it tweaks the moment at when the timeout elapsed to being specified with - /// an absolute value rather than a relative one. For more documentation see - /// the `timeout` method. - fn timeout_at(self, at: Instant) -> Timeout - where - Self::Error: From, - { - Timeout { - timeout: Delay::new_at(at), - future: self, - } - } -} - -impl TryFutureExt for F {} - -/// Future returned by the `FutureExt::timeout` method. -#[derive(Debug)] -pub struct Timeout -where - F: TryFuture, - F::Error: From, -{ - future: F, - timeout: Delay, -} - -impl Timeout -where - F: TryFuture, - F::Error: From, -{ - unsafe_pinned!(future: F); - unsafe_pinned!(timeout: Delay); -} - -impl Future for Timeout -where - F: TryFuture, - F::Error: From, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.as_mut().future().try_poll(cx) { - Poll::Pending => {} - other => return other, - } - - if self.timeout().poll(cx).is_ready() { - let err = Err(io::Error::new(io::ErrorKind::TimedOut, "future timed out").into()); - Poll::Ready(err) - } else { - Poll::Pending - } - } -} - -/// An extension trait for streams which provides convenient accessors for -/// timing out execution and such. -pub trait TryStreamExt: TryStream + Sized { - /// Creates a new stream which will take at most `dur` time to yield each - /// item of the stream. - /// - /// This combinator creates a new stream which wraps the receiving stream - /// in a timeout-per-item. The stream returned will resolve in at most - /// `dur` time for each item yielded from the stream. The first item's timer - /// starts when this method is called. - /// - /// If a stream's item completes before `dur` elapses then the timer will be - /// reset for the next item. If the timeout elapses, however, then an error - /// will be yielded on the stream and the timer will be reset. - fn timeout(self, dur: Duration) -> TimeoutStream - where - Self::Error: From, - { - TimeoutStream { - timeout: Delay::new(dur), - dur, - stream: self, - } - } -} - -impl TryStreamExt for S {} - -/// Stream returned by the `StreamExt::timeout` method. -#[derive(Debug)] -pub struct TimeoutStream -where - S: TryStream, - S::Error: From, -{ - timeout: Delay, - dur: Duration, - stream: S, -} - -impl TimeoutStream -where - S: TryStream, - S::Error: From, -{ - unsafe_pinned!(timeout: Delay); - unsafe_pinned!(stream: S); -} - -impl Stream for TimeoutStream -where - S: TryStream, - S::Error: From, -{ - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let dur = self.dur; - - let r = self.as_mut().stream().try_poll_next(cx); - match r { - Poll::Pending => {} - other => { - self.as_mut().timeout().reset(dur); - return other; - } - } - - if self.as_mut().timeout().poll(cx).is_ready() { - self.as_mut().timeout().reset(dur); - Poll::Ready(Some(Err(io::Error::new( - io::ErrorKind::TimedOut, - "stream item timed out", - ) - .into()))) - } else { - Poll::Pending - } - } -} diff --git a/ergo-rest/src/wasm_timer/timer/global.rs b/ergo-rest/src/wasm_timer/timer/global.rs deleted file mode 100644 index 6f70c5c3d..000000000 --- a/ergo-rest/src/wasm_timer/timer/global.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub(crate) use self::platform::*; - -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] -#[path = "global/desktop.rs"] -mod platform; -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -#[path = "global/wasm.rs"] -mod platform; diff --git a/ergo-rest/src/wasm_timer/timer/global/desktop.rs b/ergo-rest/src/wasm_timer/timer/global/desktop.rs deleted file mode 100644 index 737546284..000000000 --- a/ergo-rest/src/wasm_timer/timer/global/desktop.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::future::Future; -use std::io; -use std::mem::{self, ManuallyDrop}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::task::{Context, RawWaker, RawWakerVTable, Waker}; -use std::thread; -use std::thread::Thread; -use std::time::Instant; - -use pin_utils::pin_mut; - -use crate::wasm_timer::{Timer, TimerHandle}; - -pub struct HelperThread { - thread: Option>, - timer: TimerHandle, - done: Arc, -} - -impl HelperThread { - pub fn new() -> io::Result { - let timer = Timer::new(); - let timer_handle = timer.handle(); - let done = Arc::new(AtomicBool::new(false)); - let done2 = done.clone(); - let thread = thread::Builder::new().spawn(move || run(timer, done2))?; - - Ok(HelperThread { - thread: Some(thread), - done, - timer: timer_handle, - }) - } - - pub fn handle(&self) -> TimerHandle { - self.timer.clone() - } - - pub fn forget(mut self) { - self.thread.take(); - } -} - -impl Drop for HelperThread { - fn drop(&mut self) { - let thread = match self.thread.take() { - Some(thread) => thread, - None => return, - }; - self.done.store(true, Ordering::SeqCst); - thread.thread().unpark(); - drop(thread.join()); - } -} - -fn run(timer: Timer, done: Arc) { - let mut waker = current_thread_waker(); - let mut cx = Context::from_waker(&mut waker); - - pin_mut!(timer); - while !done.load(Ordering::SeqCst) { - let _ = timer.as_mut().poll(&mut cx); - timer.advance(); - match timer.next_event() { - // Ok, block for the specified time - Some(when) => { - let now = Instant::now(); - if now < when { - thread::park_timeout(when - now) - } else { - // .. continue... - } - } - - // Just wait for one of our futures to wake up - None => thread::park(), - } - } -} - -static VTABLE: RawWakerVTable = RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop); - -fn raw_clone(ptr: *const ()) -> RawWaker { - let me = ManuallyDrop::new(unsafe { Arc::from_raw(ptr as *const Thread) }); - mem::forget(me.clone()); - RawWaker::new(ptr, &VTABLE) -} - -fn raw_wake(ptr: *const ()) { - unsafe { Arc::from_raw(ptr as *const Thread) }.unpark() -} - -fn raw_wake_by_ref(ptr: *const ()) { - ManuallyDrop::new(unsafe { Arc::from_raw(ptr as *const Thread) }).unpark() -} - -fn raw_drop(ptr: *const ()) { - unsafe { Arc::from_raw(ptr as *const Thread) }; -} - -fn current_thread_waker() -> Waker { - let thread = Arc::new(thread::current()); - unsafe { Waker::from_raw(raw_clone(Arc::into_raw(thread) as *const ())) } -} diff --git a/ergo-rest/src/wasm_timer/timer/global/wasm.rs b/ergo-rest/src/wasm_timer/timer/global/wasm.rs deleted file mode 100644 index 051043d8c..000000000 --- a/ergo-rest/src/wasm_timer/timer/global/wasm.rs +++ /dev/null @@ -1,86 +0,0 @@ -use futures::task::{self, ArcWake}; -use parking_lot::Mutex; -use std::convert::TryFrom; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::task::Context; -use std::time::Duration; -use wasm_bindgen::{closure::Closure, JsCast}; - -use crate::wasm_timer::{ - timer::{Timer, TimerHandle}, - wasm::Instant, -}; - -/// Starts a background task, creates a `Timer`, and returns a handle to it. -/// -/// > **Note**: Contrary to the original `futures-timer` crate, we don't have -/// > any `forget()` method, as the task is automatically considered -/// > as "forgotten". -pub(crate) fn run() -> TimerHandle { - let timer = Timer::new(); - let handle = timer.handle(); - schedule_callback(Arc::new(Mutex::new(timer)), Duration::new(0, 0)); - handle -} - -/// Calls `Window::setTimeout` with the given `Duration`. The callback wakes up the timer and -/// processes everything. -fn schedule_callback(timer: Arc>, when: Duration) { - let window = web_sys::window().expect("Unable to access Window"); - #[allow(clippy::unwrap_used)] - let _ = window - .set_timeout_with_callback_and_timeout_and_arguments_0( - &Closure::once_into_js(move || { - let mut timer_lock = timer.lock(); - - // We start by polling the timer. If any new `Delay` is created, the waker will be used - // to wake up this task pre-emptively. As such, we pass a `Waker` that calls - // `schedule_callback` with a delay of `0`. - let waker = task::waker(Arc::new(Waker { - timer: timer.clone(), - })); - let _ = Future::poll(Pin::new(&mut *timer_lock), &mut Context::from_waker(&waker)); - - // Notify the timers that are ready. - let now = Instant::now(); - timer_lock.advance_to(now); - - // Each call to `schedule_callback` calls `schedule_callback` again, but also leaves - // the possibility for `schedule_callback` to be called in parallel. Since we don't - // want too many useless callbacks, we... - // TODO: ugh, that's a hack - if Arc::strong_count(&timer) > 20 { - return; - } - - // We call `schedule_callback` again for the next event. - let sleep_dur = timer_lock - .next_event() - .map(|next_event| { - if next_event > now { - next_event - now - } else { - Duration::new(0, 0) - } - }) - .unwrap_or(Duration::from_secs(5)); - drop(timer_lock); - schedule_callback(timer, sleep_dur); - }) - .unchecked_ref(), - i32::try_from(when.as_millis()).unwrap_or(0), - ) - .unwrap(); -} - -struct Waker { - timer: Arc>, -} - -impl ArcWake for Waker { - fn wake_by_ref(arc_self: &Arc) { - schedule_callback(arc_self.timer.clone(), Duration::new(0, 0)); - } -} diff --git a/ergo-rest/src/wasm_timer/timer/heap.rs b/ergo-rest/src/wasm_timer/timer/heap.rs deleted file mode 100644 index 9c2f44cb7..000000000 --- a/ergo-rest/src/wasm_timer/timer/heap.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! A simple binary heap with support for removal of arbitrary elements -//! -//! This heap is used to manage timer state in the event loop. All timeouts go -//! into this heap and we also cancel timeouts from this heap. The crucial -//! feature of this heap over the standard library's `BinaryHeap` is the ability -//! to remove arbitrary elements. (e.g. when a timer is canceled) -//! -//! Note that this heap is not at all optimized right now, it should hopefully -//! just work. - -use std::mem; - -pub struct Heap { - // Binary heap of items, plus the slab index indicating what position in the - // list they're in. - items: Vec<(T, usize)>, - - // A map from a slab index (assigned to an item above) to the actual index - // in the array the item appears at. - index: Vec>, - next_index: usize, -} - -enum SlabSlot { - Empty { next: usize }, - Full { value: T }, -} - -pub struct Slot { - idx: usize, -} - -impl Heap { - pub fn new() -> Heap { - Heap { - items: Vec::new(), - index: Vec::new(), - next_index: 0, - } - } - - /// Pushes an element onto this heap, returning a slot token indicating - /// where it was pushed on to. - /// - /// The slot can later get passed to `remove` to remove the element from the - /// heap, but only if the element was previously not removed from the heap. - #[allow(clippy::panic)] - pub fn push(&mut self, t: T) -> Slot { - let len = self.items.len(); - let slot = SlabSlot::Full { value: len }; - let slot_idx = if self.next_index == self.index.len() { - self.next_index += 1; - self.index.push(slot); - self.index.len() - 1 - } else { - match mem::replace(&mut self.index[self.next_index], slot) { - SlabSlot::Empty { next } => mem::replace(&mut self.next_index, next), - SlabSlot::Full { .. } => panic!(), - } - }; - self.items.push((t, slot_idx)); - self.percolate_up(len); - Slot { idx: slot_idx } - } - - pub fn peek(&self) -> Option<&T> { - self.items.get(0).map(|i| &i.0) - } - - pub fn pop(&mut self) -> Option { - if self.items.len() == 0 { - return None; - } - let slot = Slot { - idx: self.items[0].1, - }; - Some(self.remove(slot)) - } - - #[allow(clippy::panic)] - pub fn remove(&mut self, slot: Slot) -> T { - let empty = SlabSlot::Empty { - next: self.next_index, - }; - let idx = match mem::replace(&mut self.index[slot.idx], empty) { - SlabSlot::Full { value } => value, - SlabSlot::Empty { .. } => panic!(), - }; - self.next_index = slot.idx; - let (item, slot_idx) = self.items.swap_remove(idx); - debug_assert_eq!(slot.idx, slot_idx); - if idx < self.items.len() { - set_index(&mut self.index, self.items[idx].1, idx); - if self.items[idx].0 < item { - self.percolate_up(idx); - } else { - self.percolate_down(idx); - } - } - return item; - } - - fn percolate_up(&mut self, mut idx: usize) -> usize { - while idx > 0 { - let parent = (idx - 1) / 2; - if self.items[idx].0 >= self.items[parent].0 { - break; - } - let (a, b) = self.items.split_at_mut(idx); - mem::swap(&mut a[parent], &mut b[0]); - set_index(&mut self.index, a[parent].1, parent); - set_index(&mut self.index, b[0].1, idx); - idx = parent; - } - return idx; - } - - #[allow(clippy::panic)] - fn percolate_down(&mut self, mut idx: usize) -> usize { - loop { - let left = 2 * idx + 1; - let right = 2 * idx + 2; - - let mut swap_left = true; - match (self.items.get(left), self.items.get(right)) { - (Some(left), None) => { - if left.0 >= self.items[idx].0 { - break; - } - } - (Some(left), Some(right)) => { - if left.0 < self.items[idx].0 { - if right.0 < left.0 { - swap_left = false; - } - } else if right.0 < self.items[idx].0 { - swap_left = false; - } else { - break; - } - } - - (None, None) => break, - (None, Some(_right)) => { - panic!("not possible") - } - } - - let (a, b) = if swap_left { - self.items.split_at_mut(left) - } else { - self.items.split_at_mut(right) - }; - mem::swap(&mut a[idx], &mut b[0]); - set_index(&mut self.index, a[idx].1, idx); - set_index(&mut self.index, b[0].1, a.len()); - idx = a.len(); - } - return idx; - } -} - -#[allow(clippy::panic)] -fn set_index(slab: &mut Vec>, slab_slot: usize, val: T) { - match slab[slab_slot] { - SlabSlot::Full { ref mut value } => *value = val, - SlabSlot::Empty { .. } => panic!(), - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod tests { - use super::Heap; - - #[test] - fn simple() { - let mut h = Heap::new(); - h.push(1); - h.push(2); - h.push(8); - h.push(4); - assert_eq!(h.pop(), Some(1)); - assert_eq!(h.pop(), Some(2)); - assert_eq!(h.pop(), Some(4)); - assert_eq!(h.pop(), Some(8)); - assert_eq!(h.pop(), None); - assert_eq!(h.pop(), None); - } - - #[test] - fn simple2() { - let mut h = Heap::new(); - h.push(5); - h.push(4); - h.push(3); - h.push(2); - h.push(1); - assert_eq!(h.pop(), Some(1)); - h.push(8); - assert_eq!(h.pop(), Some(2)); - h.push(1); - assert_eq!(h.pop(), Some(1)); - assert_eq!(h.pop(), Some(3)); - assert_eq!(h.pop(), Some(4)); - h.push(5); - assert_eq!(h.pop(), Some(5)); - assert_eq!(h.pop(), Some(5)); - assert_eq!(h.pop(), Some(8)); - } - - #[test] - fn remove() { - let mut h = Heap::new(); - h.push(5); - h.push(4); - h.push(3); - let two = h.push(2); - h.push(1); - assert_eq!(h.pop(), Some(1)); - assert_eq!(h.remove(two), 2); - h.push(1); - assert_eq!(h.pop(), Some(1)); - assert_eq!(h.pop(), Some(3)); - } - - fn vec2heap(v: Vec) -> Heap { - let mut h = Heap::new(); - for t in v { - h.push(t); - } - return h; - } - - #[test] - fn test_peek_and_pop() { - let data = vec![2, 4, 6, 2, 1, 8, 10, 3, 5, 7, 0, 9, 1]; - let mut sorted = data.clone(); - sorted.sort(); - let mut heap = vec2heap(data); - while heap.peek().is_some() { - assert_eq!(heap.peek().unwrap(), sorted.first().unwrap()); - assert_eq!(heap.pop().unwrap(), sorted.remove(0)); - } - } - - #[test] - fn test_push() { - let mut heap = Heap::new(); - heap.push(-2); - heap.push(-4); - heap.push(-9); - assert!(*heap.peek().unwrap() == -9); - heap.push(-11); - assert!(*heap.peek().unwrap() == -11); - heap.push(-5); - assert!(*heap.peek().unwrap() == -11); - heap.push(-27); - assert!(*heap.peek().unwrap() == -27); - heap.push(-3); - assert!(*heap.peek().unwrap() == -27); - heap.push(-103); - assert!(*heap.peek().unwrap() == -103); - } - - fn check_to_vec(mut data: Vec) { - let mut heap = Heap::new(); - for data in data.iter() { - heap.push(*data); - } - data.sort(); - let mut v = Vec::new(); - while let Some(i) = heap.pop() { - v.push(i); - } - assert_eq!(v, data); - } - - #[test] - fn test_to_vec() { - check_to_vec(vec![]); - check_to_vec(vec![5]); - check_to_vec(vec![3, 2]); - check_to_vec(vec![2, 3]); - check_to_vec(vec![5, 1, 2]); - check_to_vec(vec![1, 100, 2, 3]); - check_to_vec(vec![1, 3, 5, 7, 9, 2, 4, 6, 8, 0]); - check_to_vec(vec![2, 4, 6, 2, 1, 8, 10, 3, 5, 7, 0, 9, 1]); - check_to_vec(vec![9, 11, 9, 9, 9, 9, 11, 2, 3, 4, 11, 9, 0, 0, 0, 0]); - check_to_vec(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - check_to_vec(vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]); - check_to_vec(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 1, 2]); - check_to_vec(vec![5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1]); - } - - #[test] - fn test_empty_pop() { - let mut heap = Heap::::new(); - assert!(heap.pop().is_none()); - } - - #[test] - fn test_empty_peek() { - let empty = Heap::::new(); - assert!(empty.peek().is_none()); - } -} diff --git a/ergo-rest/src/wasm_timer/timer/interval.rs b/ergo-rest/src/wasm_timer/timer/interval.rs deleted file mode 100644 index 8e389819f..000000000 --- a/ergo-rest/src/wasm_timer/timer/interval.rs +++ /dev/null @@ -1,193 +0,0 @@ -use pin_utils::unsafe_pinned; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; - -use futures::prelude::*; - -use super::delay; -use super::{Delay, Instant, TimerHandle}; - -/// A stream representing notifications at fixed interval -/// -/// Intervals are created through the `Interval::new` or -/// `Interval::new_at` methods indicating when a first notification -/// should be triggered and when it will be repeated. -/// -/// Note that intervals are not intended for high resolution timers, but rather -/// they will likely fire some granularity after the exact instant that they're -/// otherwise indicated to fire at. -#[derive(Debug)] -pub struct Interval { - delay: Delay, - interval: Duration, -} - -impl Interval { - unsafe_pinned!(delay: Delay); - - /// Creates a new interval which will fire at `dur` time into the future, - /// and will repeat every `dur` interval after - /// - /// The returned object will be bound to the default timer for this thread. - /// The default timer will be spun up in a helper thread on first use. - pub fn new(dur: Duration) -> Interval { - Interval::new_at(Instant::now() + dur, dur) - } - - /// Creates a new interval which will fire at the time specified by `at`, - /// and then will repeat every `dur` interval after - /// - /// The returned object will be bound to the default timer for this thread. - /// The default timer will be spun up in a helper thread on first use. - pub fn new_at(at: Instant, dur: Duration) -> Interval { - Interval { - delay: Delay::new_at(at), - interval: dur, - } - } - - /// Creates a new interval which will fire at the time specified by `at`, - /// and then will repeat every `dur` interval after - /// - /// The returned object will be bound to the timer specified by `handle`. - pub fn new_handle(at: Instant, dur: Duration, handle: TimerHandle) -> Interval { - Interval { - delay: Delay::new_handle(at, handle), - interval: dur, - } - } -} - -impl Stream for Interval { - type Item = (); - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if Pin::new(&mut *self).delay().poll(cx).is_pending() { - return Poll::Pending; - } - let next = next_interval(delay::fires_at(&self.delay), Instant::now(), self.interval); - self.delay.reset_at(next); - Poll::Ready(Some(())) - } -} - -/// Converts Duration object to raw nanoseconds if possible -/// -/// This is useful to divide intervals. -/// -/// While technically for large duration it's impossible to represent any -/// duration as nanoseconds, the largest duration we can represent is about -/// 427_000 years. Large enough for any interval we would use or calculate in -/// tokio. -fn duration_to_nanos(dur: Duration) -> Option { - dur.as_secs() - .checked_mul(1_000_000_000) - .and_then(|v| v.checked_add(dur.subsec_nanos() as u64)) -} - -fn next_interval(prev: Instant, now: Instant, interval: Duration) -> Instant { - let new = prev + interval; - if new > now { - return new; - } else { - #[allow(clippy::expect_used)] - let spent_ns = - duration_to_nanos(now.duration_since(prev)).expect("interval should be expired"); - #[allow(clippy::expect_used)] - let interval_ns = - duration_to_nanos(interval).expect("interval is less that 427 thousand years"); - let mult = spent_ns / interval_ns + 1; - assert!( - mult < (1 << 32), - "can't skip more than 4 billion intervals of {:?} \ - (trying to skip {})", - interval, - mult - ); - return prev + interval * (mult as u32); - } -} - -#[cfg(test)] -mod test { - use super::next_interval; - use std::time::{Duration, Instant}; - - struct Timeline(Instant); - - impl Timeline { - fn new() -> Timeline { - Timeline(Instant::now()) - } - fn at(&self, millis: u64) -> Instant { - self.0 + Duration::from_millis(millis) - } - fn at_ns(&self, sec: u64, nanos: u32) -> Instant { - self.0 + Duration::new(sec, nanos) - } - } - - fn dur(millis: u64) -> Duration { - Duration::from_millis(millis) - } - - // The math around Instant/Duration isn't 100% precise due to rounding - // errors, see #249 for more info - fn almost_eq(a: Instant, b: Instant) -> bool { - if a == b { - true - } else if a > b { - a - b < Duration::from_millis(1) - } else { - b - a < Duration::from_millis(1) - } - } - - #[test] - fn norm_next() { - let tm = Timeline::new(); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(2), dur(10)), - tm.at(11) - )); - assert!(almost_eq( - next_interval(tm.at(7777), tm.at(7788), dur(100)), - tm.at(7877) - )); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(1000), dur(2100)), - tm.at(2101) - )); - } - - #[test] - fn fast_forward() { - let tm = Timeline::new(); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(1000), dur(10)), - tm.at(1001) - )); - assert!(almost_eq( - next_interval(tm.at(7777), tm.at(8888), dur(100)), - tm.at(8977) - )); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(10000), dur(2100)), - tm.at(10501) - )); - } - - /// TODO: this test actually should be successful, but since we can't - /// multiply Duration on anything larger than u32 easily we decided - /// to allow it to fail for now - #[test] - #[should_panic(expected = "can't skip more than 4 billion intervals")] - fn large_skip() { - let tm = Timeline::new(); - assert_eq!( - next_interval(tm.at_ns(0, 1), tm.at_ns(25, 0), Duration::new(0, 2)), - tm.at_ns(25, 1) - ); - } -} diff --git a/ergo-rest/src/wasm_timer/wasm.rs b/ergo-rest/src/wasm_timer/wasm.rs deleted file mode 100644 index f2981c373..000000000 --- a/ergo-rest/src/wasm_timer/wasm.rs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2019 Pierre Krieger -// Copyright (c) 2019 Tokio Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -#![cfg(all(target_arch = "wasm32", target_os = "unknown"))] - -use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; -use std::ops::{Add, AddAssign, Sub, SubAssign}; -use std::time::Duration; - -#[derive(Debug, Copy, Clone)] -pub struct Instant { - /// Unit is milliseconds. - inner: f64, -} - -impl PartialEq for Instant { - fn eq(&self, other: &Instant) -> bool { - // Note that this will most likely only compare equal if we clone an `Instant`, - // but that's ok. - self.inner == other.inner - } -} - -impl Eq for Instant {} - -impl PartialOrd for Instant { - fn partial_cmp(&self, other: &Instant) -> Option { - self.inner.partial_cmp(&other.inner) - } -} - -impl Ord for Instant { - #[allow(clippy::unwrap_used)] - fn cmp(&self, other: &Self) -> Ordering { - self.inner.partial_cmp(&other.inner).unwrap() - } -} - -impl Instant { - pub fn now() -> Instant { - let val = web_sys::window() - .expect("not in a browser") - .performance() - .expect("performance object not available") - .now(); - Instant { inner: val } - } - - pub fn duration_since(&self, earlier: Instant) -> Duration { - *self - earlier - } - - pub fn elapsed(&self) -> Duration { - Instant::now() - *self - } -} - -impl Add for Instant { - type Output = Instant; - - fn add(self, other: Duration) -> Instant { - let new_val = self.inner + other.as_millis() as f64; - Instant { - inner: new_val as f64, - } - } -} - -impl Sub for Instant { - type Output = Instant; - - fn sub(self, other: Duration) -> Instant { - let new_val = self.inner - other.as_millis() as f64; - Instant { - inner: new_val as f64, - } - } -} - -impl Sub for Instant { - type Output = Duration; - - fn sub(self, other: Instant) -> Duration { - let ms = self.inner - other.inner; - assert!(ms >= 0.0); - Duration::from_millis(ms as u64) - } -} - -pub const UNIX_EPOCH: SystemTime = SystemTime { inner: 0.0 }; - -#[derive(Debug, Copy, Clone)] -pub struct SystemTime { - /// Unit is milliseconds. - inner: f64, -} - -impl PartialEq for SystemTime { - fn eq(&self, other: &SystemTime) -> bool { - // Note that this will most likely only compare equal if we clone an `SystemTime`, - // but that's ok. - self.inner == other.inner - } -} - -impl Eq for SystemTime {} - -impl PartialOrd for SystemTime { - fn partial_cmp(&self, other: &SystemTime) -> Option { - self.inner.partial_cmp(&other.inner) - } -} - -impl Ord for SystemTime { - #[allow(clippy::unwrap_used)] - fn cmp(&self, other: &Self) -> Ordering { - self.inner.partial_cmp(&other.inner).unwrap() - } -} - -impl SystemTime { - pub const UNIX_EPOCH: SystemTime = SystemTime { inner: 0.0 }; - - pub fn now() -> SystemTime { - let val = js_sys::Date::now(); - SystemTime { inner: val } - } - - pub fn duration_since(&self, earlier: SystemTime) -> Result { - let dur_ms = self.inner - earlier.inner; - if dur_ms < 0.0 { - return Err(()); - } - Ok(Duration::from_millis(dur_ms as u64)) - } - - pub fn elapsed(&self) -> Result { - self.duration_since(SystemTime::now()) - } - - pub fn checked_add(&self, duration: Duration) -> Option { - Some(*self + duration) - } - - pub fn checked_sub(&self, duration: Duration) -> Option { - Some(*self - duration) - } -} - -impl Add for SystemTime { - type Output = SystemTime; - - fn add(self, other: Duration) -> SystemTime { - let new_val = self.inner + other.as_millis() as f64; - SystemTime { - inner: new_val as f64, - } - } -} - -impl Sub for SystemTime { - type Output = SystemTime; - - fn sub(self, other: Duration) -> SystemTime { - let new_val = self.inner - other.as_millis() as f64; - SystemTime { - inner: new_val as f64, - } - } -} - -impl AddAssign for SystemTime { - fn add_assign(&mut self, rhs: Duration) { - *self = *self + rhs; - } -} - -impl SubAssign for SystemTime { - fn sub_assign(&mut self, rhs: Duration) { - *self = *self - rhs; - } -} diff --git a/ergoscript-compiler/Cargo.toml b/ergoscript-compiler/Cargo.toml index 89c106c58..ba9df0de5 100644 --- a/ergoscript-compiler/Cargo.toml +++ b/ergoscript-compiler/Cargo.toml @@ -20,7 +20,7 @@ num-derive = { workspace = true } num-traits = { workspace = true } logos = "0.12" text-size = "1.1.0" -rowan = "0.12.1" +rowan = "0.16.1" drop_bomb = "0.1.5" line-col = "0.2.1" diff --git a/ergoscript-compiler/src/parser/source.rs b/ergoscript-compiler/src/parser/source.rs index 72bea6723..f423cbd5a 100644 --- a/ergoscript-compiler/src/parser/source.rs +++ b/ergoscript-compiler/src/parser/source.rs @@ -27,7 +27,7 @@ impl<'t, 'input> Source<'t, 'input> { self.peek_kind_raw() } - pub fn peek_token(&mut self) -> Option<&Token> { + pub fn peek_token(&mut self) -> Option<&Token<'_>> { self.eat_trivia(); self.peek_token_raw() } @@ -50,7 +50,7 @@ impl<'t, 'input> Source<'t, 'input> { self.peek_token_raw().map(|Token { kind, .. }| *kind) } - fn peek_token_raw(&self) -> Option<&Token> { + fn peek_token_raw(&self) -> Option<&Token<'_>> { self.tokens.get(self.cursor) } } diff --git a/ergoscript-compiler/src/syntax.rs b/ergoscript-compiler/src/syntax.rs index 81052e321..3c76431d3 100644 --- a/ergoscript-compiler/src/syntax.rs +++ b/ergoscript-compiler/src/syntax.rs @@ -4,7 +4,7 @@ use super::lexer::TokenKind; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, PartialOrd, Ord, Hash)] pub enum SyntaxKind { Whitespace, FnKw, diff --git a/ergotree-interpreter/Cargo.toml b/ergotree-interpreter/Cargo.toml index ff4e20e18..af1df292e 100644 --- a/ergotree-interpreter/Cargo.toml +++ b/ergotree-interpreter/Cargo.toml @@ -40,6 +40,9 @@ miette = { workspace = true, optional = true } hashbrown = { workspace = true } core2 = { workspace = true } sigma-test-util = { workspace = true, optional = true } + +generic-array = "=0.14.7" #TODO: unpin this when k256 and elliptic-curve upgrade to generic-array 1.0 and remove the deprecation warnings + [features] json = [ "serde", diff --git a/ergotree-interpreter/src/sigma_protocol.rs b/ergotree-interpreter/src/sigma_protocol.rs index fb10dc8cf..a7aa6a395 100644 --- a/ergotree-interpreter/src/sigma_protocol.rs +++ b/ergotree-interpreter/src/sigma_protocol.rs @@ -120,6 +120,6 @@ mod tests { assert!(SOUNDNESS_BITS < GROUP_SIZE_BITS); // blake2b hash function requirements assert!(SOUNDNESS_BYTES * 8 <= 512); - assert!(SOUNDNESS_BYTES % 8 == 0); + assert!(SOUNDNESS_BYTES.is_multiple_of(8)); } } diff --git a/ergotree-interpreter/src/sigma_protocol/proof_tree.rs b/ergotree-interpreter/src/sigma_protocol/proof_tree.rs index 887ba375a..65302381f 100644 --- a/ergotree-interpreter/src/sigma_protocol/proof_tree.rs +++ b/ergotree-interpreter/src/sigma_protocol/proof_tree.rs @@ -44,7 +44,7 @@ impl ProofTree { } } - pub(crate) fn as_tree_kind(&self) -> ProofTreeKind { + pub(crate) fn as_tree_kind(&self) -> ProofTreeKind<'_> { match self { ProofTree::UncheckedTree(unch) => unch.as_tree_kind(), ProofTree::UnprovenTree(unp) => unp.as_tree_kind(), diff --git a/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs b/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs index 48d7841ff..ca3838f07 100644 --- a/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs +++ b/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs @@ -47,7 +47,7 @@ impl UncheckedTree { } } - pub(crate) fn as_tree_kind(&self) -> ProofTreeKind { + pub(crate) fn as_tree_kind(&self) -> ProofTreeKind<'_> { match self { UncheckedTree::UncheckedLeaf(ul) => ProofTreeKind::Leaf(ul), UncheckedTree::UncheckedConjecture(uc) => ProofTreeKind::Conjecture(uc), diff --git a/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs index b6de29614..4ab3f6974 100644 --- a/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs +++ b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs @@ -65,7 +65,7 @@ impl UnprovenTree { } } - pub(crate) fn as_tree_kind(&self) -> ProofTreeKind { + pub(crate) fn as_tree_kind(&self) -> ProofTreeKind<'_> { match self { UnprovenTree::UnprovenLeaf(ul) => ProofTreeKind::Leaf(ul), UnprovenTree::UnprovenConjecture(uc) => ProofTreeKind::Conjecture(uc), diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml index 241e9ff94..2e2212540 100644 --- a/ergotree-ir/Cargo.toml +++ b/ergotree-ir/Cargo.toml @@ -40,7 +40,7 @@ strum_macros = { version = "0.26.4", default-features = false } miette = { workspace = true, optional = true } hashbrown = { workspace = true } core2 = { workspace = true } -foldhash = { version = "0.1.3", default-features = false } +foldhash = { version = "0.2.0", default-features = false } [features] default = ["json", "std"] arbitrary = ["std", "proptest", "proptest-derive", "ergo-chain-types/arbitrary"] diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs index 218f6b0ec..3a1c1a0fa 100644 --- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs +++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs @@ -421,7 +421,6 @@ mod arbitrary { #[allow(clippy::panic)] #[cfg(test)] #[cfg(feature = "arbitrary")] -#[allow(clippy::panic)] mod tests { use super::*; use crate::serialization::sigma_serialize_roundtrip; diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs index 9bb0eec91..025c25e18 100644 --- a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs +++ b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs @@ -103,7 +103,6 @@ mod arbitrary { } } -#[allow(clippy::panic)] #[allow(clippy::unwrap_used)] #[allow(clippy::panic)] #[cfg(test)] diff --git a/ergotree-ir/src/source_span.rs b/ergotree-ir/src/source_span.rs index 48def6669..8116be14b 100644 --- a/ergotree-ir/src/source_span.rs +++ b/ergotree-ir/src/source_span.rs @@ -63,7 +63,7 @@ impl From<(usize, usize)> for SourceSpan { #[cfg(feature = "std")] impl From for miette::SourceSpan { fn from(value: SourceSpan) -> Self { - miette::SourceSpan::new(value.offset.into(), value.length.into()) + miette::SourceSpan::new(value.offset.into(), value.length) } } diff --git a/rust-toolchain b/rust-toolchain index 8a2b13383..8d3947429 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.87 +1.91.1