diff --git a/.github/workflows/rust_tests.yml b/.github/workflows/rust_tests.yml index de8bd8f..a7a2f83 100644 --- a/.github/workflows/rust_tests.yml +++ b/.github/workflows/rust_tests.yml @@ -22,7 +22,7 @@ jobs: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl - wasm32-unknown-unknown - - wasm32-wasi + - wasm32-wasip1 steps: - uses: actions/checkout@v2 @@ -104,14 +104,14 @@ jobs: with: command: build toolchain: ${{ matrix.toolchain }} - args: --target ${{ matrix.target }} --features "http-binding hyper hyper_wasi" + args: --target ${{ matrix.target }} --features "http-0-2-binding hyper-0-14 hyper_wasi" - uses: actions-rs/cargo@v1 name: "Test" if: matrix.target == 'wasm32-wasi' with: command: test toolchain: ${{ matrix.toolchain }} - args: --target ${{ matrix.target }} --features "http-binding hyper hyper_wasi" + args: --target ${{ matrix.target }} --features "http-0-2-binding hyper-0-14 hyper_wasi" env: CARGO_TARGET_WASM32_WASI_RUNNER: wasmedge # Build examples diff --git a/Cargo.toml b/Cargo.toml index bb72c95..008e110 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cloudevents-sdk" -version = "0.7.0" +version = "0.8.0" authors = ["Francesco Guardiani "] license-file = "LICENSE" edition = "2018" @@ -23,57 +23,60 @@ name = "cloudevents" [features] http-binding = ["async-trait", "bytes", "futures", "http"] -actix = ["actix-web", "actix-http", "async-trait", "bytes", "futures", "http"] +http-0-2-binding = ["async-trait", "bytes", "futures", "http-0-2"] +actix = ["actix-web", "actix-http", "async-trait", "bytes", "futures", "http-0-2"] reqwest = ["reqwest-lib", "async-trait", "bytes", "http", "uuid/js"] rdkafka = ["rdkafka-lib", "bytes", "futures"] -warp = ["warp-lib", "bytes", "http", "hyper"] -axum = ["bytes", "http", "hyper", "axum-lib", "http-body", "async-trait"] -poem = ["bytes", "http", "poem-lib", "hyper", "async-trait"] +warp = ["warp-lib", "bytes", "http-0-2", "http-body-util", "hyper-0-14"] +axum = ["bytes", "http", "hyper", "axum-lib", "http-body-util", "async-trait"] +poem = ["bytes", "http", "poem-lib", "hyper", "async-trait", "http-body-util", "futures"] nats = ["nats-lib"] [dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" chrono = { version = "^0.4", features = ["serde"] } -delegate-attr = "^0.2" -base64 = "^0.12" -url = { version = "^2.1", features = ["serde"] } -snafu = "^0.6" -bitflags = "^1.2" +delegate-attr = "^0.3" +base64 = "^0.22" +url = { version = "^2.5", features = ["serde"] } +snafu = "^0.8" +bitflags = "^2.6" uuid = { version = "1", features = ["v4"] } # runtime optional deps actix-web = { version = "4", optional = true } actix-http = { version = "3", optional = true } -reqwest-lib = { version = "^0.11", default-features = false, features = ["rustls-tls"], optional = true, package = "reqwest" } +reqwest-lib = { version = "^0.12", default-features = false, features = ["rustls-tls"], optional = true, package = "reqwest" } rdkafka-lib = { version = "^0.36", features = ["cmake-build"], optional = true, package = "rdkafka" } warp-lib = { version = "^0.3", optional = true, package = "warp" } -async-trait = { version = "^0.1.33", optional = true } +async-trait = { version = "^0.1", optional = true } bytes = { version = "^1.0", optional = true } -futures = { version = "^0.3", optional = true } -http = { version = "0.2", optional = true } -axum-lib = { version = "^0.6", optional = true, package="axum"} -http-body = { version = "^0.4", optional = true } -poem-lib = { version = "=1.2.34", optional = true, package = "poem" } -nats-lib = { version = "0.21.0", optional = true, package = "nats" } +futures = { version = "^0.3", optional = true, features = ["compat"]} +http = { version = "1.1", optional = true} +http-0-2 = { version = "0.2", optional = true, package = "http"} +axum-lib = { version = "^0.7", optional = true, package="axum"} +http-body-util = {version = "^0.1", optional = true} +poem-lib = { version = "^3.1", optional = true, package = "poem" } +nats-lib = { version = "0.25.0", optional = true, package = "nats" } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] -hostname = "^0.3" +hostname = "^0.4" [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] web-sys = { version = "^0.3", features = ["Window", "Location"] } [target.'cfg(not(target_os = "wasi"))'.dependencies] -hyper = { version = "^0.14", optional = true } +hyper = { version = "^1.4", optional = true, package="hyper" } +hyper-0-14 = { version = "^0.14", optional = true, package = "hyper"} [target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies] hyper_wasi = { version = "0.15", features = ["full"], optional = true } [dev-dependencies] -rstest = "0.6" +rstest = "0.23" claims = "0.7.1" version-sync = "0.9.2" -serde_yaml = "0.8" +serde_yaml = "^0.9" rmp-serde = "1" # runtime dev-deps @@ -81,14 +84,14 @@ rmp-serde = "1" url = { version = "^2.1", features = ["serde"] } serde_json = { version = "^1.0" } chrono = { version = "^0.4", features = ["serde"] } -mockito = "0.25.1" +mockito = "0.31.1" mime = "0.3" [target.'cfg(not(target_os = "wasi"))'.dev-dependencies] actix-rt = { version = "^2" } tokio = { version = "^1.0", features = ["full"] } -tower = { version = "0.4", features = ["util"] } +tower = { version = "0.5", features = ["util"] } [target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dev-dependencies] tokio_wasi = { version = "1", features = [ diff --git a/README.md b/README.md index 904163f..3c8d06f 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ enabling your Protocol Binding of choice: ```toml [dependencies] -cloudevents-sdk = { version = "0.7.0" } +cloudevents-sdk = { version = "0.8.0" } ``` Now you can start creating events: diff --git a/example-projects/actix-web-example/Cargo.toml b/example-projects/actix-web-example/Cargo.toml index 7d65f90..99af967 100644 --- a/example-projects/actix-web-example/Cargo.toml +++ b/example-projects/actix-web-example/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] cloudevents-sdk = { path = "../..", features = ["actix"] } actix-web = "4" -actix-cors = "0.6.0-beta.8" +actix-cors = "^0.7" serde_json = "^1.0" url = { version = "^2.1" } -env_logger = "0.7.1" +env_logger = "^0.11" diff --git a/example-projects/axum-example/Cargo.toml b/example-projects/axum-example/Cargo.toml index a369f24..c1dc625 100644 --- a/example-projects/axum-example/Cargo.toml +++ b/example-projects/axum-example/Cargo.toml @@ -6,16 +6,16 @@ edition = "2021" [dependencies] cloudevents-sdk = { path = "../..", features = ["axum"] } -axum = "^0.6" -http = "^0.2" +axum = "^0.7" +http = "^1.1" tokio = { version = "^1", features = ["full"] } tracing = "^0.1" -tracing-subscriber = "^0.2" -tower-http = { version = "^0.1", features = ["trace"] } +tracing-subscriber = "^0.3" +tower-http = { version = "^0.5", features = ["trace"] } [dev-dependencies] tower = { version = "^0.4", features = ["util"] } serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" chrono = { version = "^0.4", features = ["serde"] } -hyper = { version = "^0.14" } +hyper = { version = "^1.4" } diff --git a/example-projects/axum-example/src/main.rs b/example-projects/axum-example/src/main.rs index 27a7b9a..0c0c229 100644 --- a/example-projects/axum-example/src/main.rs +++ b/example-projects/axum-example/src/main.rs @@ -4,7 +4,6 @@ use axum::{ }; use cloudevents::Event; use http::StatusCode; -use std::net::SocketAddr; use tower_http::trace::TraceLayer; fn echo_app() -> Router { @@ -27,12 +26,8 @@ async fn main() { } tracing_subscriber::fmt::init(); let service = echo_app(); - let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); - tracing::debug!("listening on {}", addr); - axum::Server::bind(&addr) - .serve(service.into_make_service()) - .await - .unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); + axum::serve(listener, service).await.unwrap(); } #[cfg(test)] diff --git a/example-projects/nats-example/Cargo.toml b/example-projects/nats-example/Cargo.toml index c038d77..cdf4ed7 100644 --- a/example-projects/nats-example/Cargo.toml +++ b/example-projects/nats-example/Cargo.toml @@ -9,4 +9,4 @@ edition = "2021" [dependencies] cloudevents-sdk = { path = "../..", features = ["nats"] } serde_json = "^1.0" -nats = "0.21.0" \ No newline at end of file +nats = "^0.25" \ No newline at end of file diff --git a/example-projects/poem-example/Cargo.toml b/example-projects/poem-example/Cargo.toml index da36729..7b7acf4 100644 --- a/example-projects/poem-example/Cargo.toml +++ b/example-projects/poem-example/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" cloudevents-sdk = { path = "../..", features = ["poem"] } tokio = { version = "1.13", features = ["macros", "rt-multi-thread"] } tracing = "0.1" -poem = { version = "1" } -tracing-subscriber = "0.2" +poem = { version = "^3.0" } +tracing-subscriber = "0.3" serde_json = "1.0" [dev-dependencies] diff --git a/example-projects/reqwest-wasm-example/Cargo.toml b/example-projects/reqwest-wasm-example/Cargo.toml index b35fb03..1be202c 100644 --- a/example-projects/reqwest-wasm-example/Cargo.toml +++ b/example-projects/reqwest-wasm-example/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -reqwest = "^0.11" +reqwest = "^0.12" uuid = "1" cloudevents-sdk = { path = "../..", features = ["reqwest"] } url = { version = "^2.1" } diff --git a/example-projects/wasi-example/Cargo.toml b/example-projects/wasi-example/Cargo.toml index 91e863c..5adf096 100644 --- a/example-projects/wasi-example/Cargo.toml +++ b/example-projects/wasi-example/Cargo.toml @@ -5,11 +5,11 @@ edition = "2021" [dependencies] anyhow = "1.0" -cloudevents-sdk = { path = "../..", features = ["http-binding", "hyper_wasi", "hyper" ] } +cloudevents-sdk = { path = "../..", features = ["http-0-2-binding", "hyper_wasi", "hyper-0-14" ] } hyper_wasi = { version = "0.15", features = ["full"] } log = "0.4.21" tokio_wasi = { version = "1", features = ["io-util", "fs", "net", "time", "rt", "macros"] } -serde_json = " 1.0.116" +serde_json = "^1.0" [dev-dependencies] bytes = "1.6.0" diff --git a/example-projects/wasi-example/src/main.rs b/example-projects/wasi-example/src/main.rs index 6e3fbdf..09ec48d 100644 --- a/example-projects/wasi-example/src/main.rs +++ b/example-projects/wasi-example/src/main.rs @@ -1,5 +1,5 @@ -use cloudevents::binding::http::builder::adapter::to_response; -use cloudevents::binding::http::to_event; +use cloudevents::binding::http_0_2::builder::adapter::to_response; +use cloudevents::binding::http_0_2::to_event; use hyper::service::{make_service_fn, service_fn}; use hyper::Server; @@ -23,7 +23,9 @@ async fn main() -> Result<(), Box> { } Ok(()) } -async fn handle_request(req: Request) -> Result, anyhow::Error> { +async fn handle_request( + req: Request, +) -> Result, anyhow::Error> { match (req.method(), req.uri().path()) { (&Method::POST, "/") => { let headers = req.headers().clone(); @@ -34,7 +36,9 @@ async fn handle_request(req: Request) -> Result, anyhow::Er to_response(_respevt).map_err(|err| err.into()) } - (&Method::GET, "/health/readiness") => Ok(Response::new(Body::from(""))), + (&Method::GET, "/health/readiness") => { + Ok(Response::new(Body::from(""))) + } (&Method::GET, "/health/liveness") => Ok(Response::new(Body::from(""))), _ => { let mut not_found = Response::default(); diff --git a/src/binding/actix/server_request.rs b/src/binding/actix/server_request.rs index 67f4b12..60e168c 100644 --- a/src/binding/actix/server_request.rs +++ b/src/binding/actix/server_request.rs @@ -1,4 +1,4 @@ -use crate::binding::http::{to_event, Headers}; +use crate::binding::http_0_2::{to_event, Headers}; use crate::Event; use actix_web::dev::Payload; use actix_web::web::BytesMut; @@ -6,6 +6,7 @@ use actix_web::{web, HttpRequest}; use async_trait::async_trait; use futures::{future::LocalBoxFuture, FutureExt, StreamExt}; use http::header::{AsHeaderName, HeaderName, HeaderValue}; +use http_0_2 as http; /// Implement Headers for the actix HeaderMap impl<'a> Headers<'a> for actix_http::header::HeaderMap { diff --git a/src/binding/actix/server_response.rs b/src/binding/actix/server_response.rs index 9f7a36c..11c3093 100644 --- a/src/binding/actix/server_response.rs +++ b/src/binding/actix/server_response.rs @@ -1,8 +1,9 @@ -use crate::binding::http::{Builder, Serializer}; +use crate::binding::http_0_2::{Builder, Serializer}; use crate::message::{BinaryDeserializer, Result}; use crate::Event; use actix_web::http::StatusCode; use actix_web::{HttpRequest, HttpResponse, HttpResponseBuilder}; +use http_0_2 as http; impl Builder for HttpResponseBuilder { fn header(&mut self, key: &str, value: http::header::HeaderValue) { diff --git a/src/binding/axum/extract.rs b/src/binding/axum/extract.rs index fd03379..549df0a 100644 --- a/src/binding/axum/extract.rs +++ b/src/binding/axum/extract.rs @@ -1,45 +1,46 @@ -use axum_lib as axum; - use async_trait::async_trait; -use axum::extract::FromRequest; -use axum::http::Request; -use http::request::Parts; +use axum::body::Bytes; +use axum::extract::{FromRequest, Request}; +use axum::response::Response; +use axum_lib as axum; +use http; use http::StatusCode; -use http_body::Body; -use hyper::body; use crate::binding::http::to_event; use crate::event::Event; -type BoxError = Box; - #[async_trait] -impl FromRequest for Event +impl FromRequest for Event where - B: Body + Send + 'static, - B::Data: Send, - B::Error: Into, + Bytes: FromRequest, S: Send + Sync, { - type Rejection = (StatusCode, String); + type Rejection = Response; - async fn from_request(req: Request, _state: &S) -> Result { - let (Parts { headers, .. }, req_body) = req.into_parts(); - let buf = body::to_bytes(req_body) - .await - .map_err(|e| (StatusCode::BAD_REQUEST, format!("{}", e.into())))? - .to_vec(); + async fn from_request(req: Request, _state: &S) -> Result { + let (parts, body) = req.into_parts(); - to_event(&headers, buf).map_err(|e| (StatusCode::BAD_REQUEST, format!("{}", e))) + let body = axum::body::to_bytes(body, usize::MAX).await.map_err(|e| { + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(axum::body::Body::from(e.to_string())) + .unwrap() + })?; + + to_event(&parts.headers, body.to_vec()).map_err(|e| { + Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(axum::body::Body::from(e.to_string())) + .unwrap() + }) } } #[cfg(test)] mod tests { - use axum_lib as axum; - use super::*; use axum::body::Body; + use axum::extract::FromRequest; use axum::http::{self, Request, StatusCode}; use crate::test::fixtures; @@ -80,7 +81,7 @@ mod tests { assert!(result.is_err()); let rejection = result.unwrap_err(); - let reason = rejection.0; + let reason = rejection.status(); assert_eq!(reason, StatusCode::BAD_REQUEST) } diff --git a/src/binding/axum/mod.rs b/src/binding/axum/mod.rs index cb7b8a3..5e90cac 100644 --- a/src/binding/axum/mod.rs +++ b/src/binding/axum/mod.rs @@ -155,7 +155,7 @@ mod tests { ); let (_, body) = resp.into_parts(); - let body = hyper::body::to_bytes(body).await.unwrap(); + let body = axum::body::to_bytes(body, usize::MAX).await.unwrap(); assert_eq!(j.to_string().as_bytes(), body); } diff --git a/src/binding/axum/response.rs b/src/binding/axum/response.rs index 3f4b956..3e9d3a4 100644 --- a/src/binding/axum/response.rs +++ b/src/binding/axum/response.rs @@ -1,27 +1,21 @@ -use axum_lib as axum; - -use axum::{ - body::{boxed, BoxBody}, - http::Response, - response::IntoResponse, -}; -use http::{header, StatusCode}; -use hyper::body::Body; - use crate::binding::http::builder::adapter::to_response; use crate::event::Event; +use axum::{body::Body, http::Response, response::IntoResponse}; +use axum_lib as axum; +use http; +use http::{header, StatusCode}; impl IntoResponse for Event { - fn into_response(self) -> Response { + fn into_response(self) -> Response { match to_response(self) { Ok(resp) => { let (parts, body) = resp.into_parts(); - Response::from_parts(parts, boxed(body)) + Response::from_parts(parts, Body::new(body)) } Err(err) => Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .header(header::CONTENT_TYPE, "text/plain") - .body(boxed(Body::from(err.to_string()))) + .body(Body::from(err.to_string())) .unwrap(), } } @@ -105,7 +99,7 @@ mod tests { ); let (_, body) = resp.into_parts(); - let body = hyper::body::to_bytes(body).await.unwrap(); + let body = axum::body::to_bytes(body, usize::MAX).await.unwrap(); assert_eq!(fixtures::json_data_binary(), body); } diff --git a/src/binding/http/builder/adapter.rs b/src/binding/http/builder/adapter.rs index afdd497..aab2695 100644 --- a/src/binding/http/builder/adapter.rs +++ b/src/binding/http/builder/adapter.rs @@ -1,33 +1,38 @@ +use bytes::Bytes; use http::Response; -use hyper::body::Body; +use http_body_util::Full; use std::cell::Cell; use crate::binding::http::{Builder, Serializer}; use crate::message::{BinaryDeserializer, Error, Result}; use crate::Event; +use std::convert::Infallible; +type BoxBody = http_body_util::combinators::UnsyncBoxBody; struct Adapter { builder: Cell, } -impl Builder> for Adapter { +impl Builder> for Adapter { fn header(&mut self, key: &str, value: http::header::HeaderValue) { self.builder.set(self.builder.take().header(key, value)); } - fn body(&mut self, bytes: Vec) -> Result> { + + fn body(&mut self, bytes: Vec) -> Result> { self.builder .take() - .body(Body::from(bytes)) + .body(BoxBody::new(Full::from(bytes))) .map_err(|e| crate::message::Error::Other { source: Box::new(e), }) } - fn finish(&mut self) -> Result> { + + fn finish(&mut self) -> Result> { self.body(Vec::new()) } } -pub fn to_response(event: Event) -> std::result::Result, Error> { +pub fn to_response(event: Event) -> std::result::Result, Error> { BinaryDeserializer::deserialize_binary( event, Serializer::new(Adapter { diff --git a/src/binding/http/builder/mod.rs b/src/binding/http/builder/mod.rs index 81b6381..b486fb7 100644 --- a/src/binding/http/builder/mod.rs +++ b/src/binding/http/builder/mod.rs @@ -3,6 +3,8 @@ pub mod adapter; use crate::message::Result; +use http; + pub trait Builder { fn header(&mut self, key: &str, value: http::header::HeaderValue); fn body(&mut self, bytes: Vec) -> Result; diff --git a/src/binding/http/deserializer.rs b/src/binding/http/deserializer.rs index 21a1c80..782184f 100644 --- a/src/binding/http/deserializer.rs +++ b/src/binding/http/deserializer.rs @@ -8,6 +8,8 @@ use crate::{ Result, StructuredDeserializer, StructuredSerializer, }, }; + +use http; use std::convert::TryFrom; pub struct Deserializer<'a, T: Headers<'a>> { diff --git a/src/binding/http/headers.rs b/src/binding/http/headers.rs index a129d87..e8f850f 100644 --- a/src/binding/http/headers.rs +++ b/src/binding/http/headers.rs @@ -1,5 +1,7 @@ use http::header::{AsHeaderName, HeaderMap, HeaderName, HeaderValue}; +use http; + /// Any http library should be able to use the /// [`to_event`](super::to_event) function with an implementation of /// this trait. diff --git a/src/binding/http/mod.rs b/src/binding/http/mod.rs index 52b0e03..7437fd0 100644 --- a/src/binding/http/mod.rs +++ b/src/binding/http/mod.rs @@ -13,6 +13,8 @@ mod serializer; pub use builder::Builder; use core::convert::TryFrom; use http::Response; + +use http; pub use serializer::Serializer; use std::convert::TryInto; use std::fmt::Debug; @@ -53,6 +55,8 @@ mod tests { use core::convert::TryFrom; use http::Response; + use http; + #[test] fn test_response_to_event() { let event = fixtures::v10::minimal_string_extension(); diff --git a/src/binding/http/serializer.rs b/src/binding/http/serializer.rs index 30ca8e1..3f63b5b 100644 --- a/src/binding/http/serializer.rs +++ b/src/binding/http/serializer.rs @@ -12,6 +12,8 @@ use crate::message::{ }; use crate::Event; use http::Request; + +use http; use std::convert::TryFrom; use std::fmt::Debug; @@ -131,6 +133,8 @@ mod tests { use crate::test::fixtures; use bytes::Bytes; use http::Request; + + use http; use std::convert::TryFrom; #[test] diff --git a/src/binding/http_0_2/builder/adapter.rs b/src/binding/http_0_2/builder/adapter.rs new file mode 100644 index 0000000..da12a6f --- /dev/null +++ b/src/binding/http_0_2/builder/adapter.rs @@ -0,0 +1,44 @@ +use http::Response; +use http_0_2 as http; +use hyper::body::Body; +use std::cell::Cell; + +#[cfg(not(target_os = "wasi"))] +use hyper_0_14 as hyper; + +#[cfg(target_os = "wasi")] +use hyper; + +use crate::binding::http_0_2::{Builder, Serializer}; +use crate::message::{BinaryDeserializer, Error, Result}; +use crate::Event; + +struct Adapter { + builder: Cell, +} + +impl Builder> for Adapter { + fn header(&mut self, key: &str, value: http::header::HeaderValue) { + self.builder.set(self.builder.take().header(key, value)); + } + fn body(&mut self, bytes: Vec) -> Result> { + self.builder + .take() + .body(Body::from(bytes)) + .map_err(|e| crate::message::Error::Other { + source: Box::new(e), + }) + } + fn finish(&mut self) -> Result> { + self.body(Vec::new()) + } +} + +pub fn to_response(event: Event) -> std::result::Result, Error> { + BinaryDeserializer::deserialize_binary( + event, + Serializer::new(Adapter { + builder: Cell::new(http::Response::builder()), + }), + ) +} diff --git a/src/binding/http_0_2/builder/mod.rs b/src/binding/http_0_2/builder/mod.rs new file mode 100644 index 0000000..91e7103 --- /dev/null +++ b/src/binding/http_0_2/builder/mod.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "hyper-0-14")] +pub mod adapter; + +use crate::message::Result; +use http_0_2 as http; + +pub trait Builder { + fn header(&mut self, key: &str, value: http::header::HeaderValue); + fn body(&mut self, bytes: Vec) -> Result; + fn finish(&mut self) -> Result; +} diff --git a/src/binding/http_0_2/deserializer.rs b/src/binding/http_0_2/deserializer.rs new file mode 100644 index 0000000..414137b --- /dev/null +++ b/src/binding/http_0_2/deserializer.rs @@ -0,0 +1,101 @@ +use super::{Headers, SPEC_VERSION_HEADER}; +use crate::{ + binding::CLOUDEVENTS_JSON_HEADER, + event::SpecVersion, + header_value_to_str, message, + message::{ + BinaryDeserializer, BinarySerializer, Encoding, MessageAttributeValue, MessageDeserializer, + Result, StructuredDeserializer, StructuredSerializer, + }, +}; +use http_0_2 as http; +use std::convert::TryFrom; + +pub struct Deserializer<'a, T: Headers<'a>> { + headers: &'a T, + body: Vec, +} + +impl<'a, T: Headers<'a>> Deserializer<'a, T> { + pub fn new(headers: &'a T, body: Vec) -> Deserializer<'a, T> { + Deserializer { headers, body } + } +} + +impl<'a, T: Headers<'a>> BinaryDeserializer for Deserializer<'a, T> { + fn deserialize_binary>(self, mut visitor: V) -> Result { + if self.encoding() != Encoding::BINARY { + return Err(message::Error::WrongEncoding {}); + } + + let spec_version = SpecVersion::try_from( + self.headers + .get(SPEC_VERSION_HEADER) + .map(|a| header_value_to_str!(a)) + .unwrap()?, + )?; + + let attributes = spec_version.attribute_names(); + + visitor = visitor.set_spec_version(spec_version)?; + + for (hn, hv) in self.headers.iter().filter(|(hn, _)| { + let key = hn.as_str(); + SPEC_VERSION_HEADER.ne(key) && key.starts_with("ce-") + }) { + let name = &hn.as_str()["ce-".len()..]; + + if attributes.contains(&name) { + visitor = visitor.set_attribute( + name, + MessageAttributeValue::String(String::from(header_value_to_str!(hv)?)), + )? + } else { + visitor = visitor.set_extension( + name, + MessageAttributeValue::String(String::from(header_value_to_str!(hv)?)), + )? + } + } + + if let Some(hv) = self.headers.get(http::header::CONTENT_TYPE) { + visitor = visitor.set_attribute( + "datacontenttype", + MessageAttributeValue::String(String::from(header_value_to_str!(hv)?)), + )? + } + + if !self.body.is_empty() { + visitor.end_with_data(self.body) + } else { + visitor.end() + } + } +} + +impl<'a, T: Headers<'a>> StructuredDeserializer for Deserializer<'a, T> { + fn deserialize_structured>(self, visitor: V) -> Result { + if self.encoding() != Encoding::STRUCTURED { + return Err(message::Error::WrongEncoding {}); + } + visitor.set_structured_event(self.body) + } +} + +impl<'a, T: Headers<'a>> MessageDeserializer for Deserializer<'a, T> { + fn encoding(&self) -> Encoding { + if self + .headers + .get(http::header::CONTENT_TYPE) + .and_then(|v| v.to_str().ok()) + .filter(|&v| v.starts_with(CLOUDEVENTS_JSON_HEADER)) + .is_some() + { + Encoding::STRUCTURED + } else if self.headers.get(SPEC_VERSION_HEADER).is_some() { + Encoding::BINARY + } else { + Encoding::UNKNOWN + } + } +} diff --git a/src/binding/http_0_2/headers.rs b/src/binding/http_0_2/headers.rs new file mode 100644 index 0000000..31136a5 --- /dev/null +++ b/src/binding/http_0_2/headers.rs @@ -0,0 +1,22 @@ +use http::header::{AsHeaderName, HeaderMap, HeaderName, HeaderValue}; +use http_0_2 as http; + +/// Any http library should be able to use the +/// [`to_event`](super::to_event) function with an implementation of +/// this trait. +pub trait Headers<'a> { + type Iterator: Iterator; + fn get(&self, name: K) -> Option<&HeaderValue>; + fn iter(&'a self) -> Self::Iterator; +} + +/// Implemention for the HeaderMap used by warp/reqwest +impl<'a> Headers<'a> for HeaderMap { + type Iterator = http::header::Iter<'a, HeaderValue>; + fn get(&self, name: K) -> Option<&HeaderValue> { + self.get(name) + } + fn iter(&'a self) -> Self::Iterator { + self.iter() + } +} diff --git a/src/binding/http_0_2/mod.rs b/src/binding/http_0_2/mod.rs new file mode 100644 index 0000000..7984d19 --- /dev/null +++ b/src/binding/http_0_2/mod.rs @@ -0,0 +1,73 @@ +pub mod builder; +pub mod deserializer; +mod headers; + +use crate::{ + message::{Error, MessageDeserializer}, + Event, +}; +use deserializer::Deserializer; +pub use headers::Headers; +mod serializer; + +pub use builder::Builder; +use core::convert::TryFrom; +use http::Response; +use http_0_2 as http; +pub use serializer::Serializer; +use std::convert::TryInto; +use std::fmt::Debug; + +pub static SPEC_VERSION_HEADER: &str = "ce-specversion"; + +/// Turn a pile of HTTP headers and a body into a CloudEvent +pub fn to_event<'a, T: Headers<'a>>( + headers: &'a T, + body: Vec, +) -> std::result::Result { + MessageDeserializer::into_event(Deserializer::new(headers, body)) +} + +pub fn header_prefix(name: &str) -> String { + super::header_prefix("ce-", name) +} + +impl TryFrom> for Event +where + T: TryInto>, + >>::Error: Debug, +{ + type Error = crate::message::Error; + + fn try_from(response: Response) -> Result { + let headers = response.headers().to_owned(); + let body = T::try_into(response.into_body()).unwrap(); + + to_event(&headers, body) + } +} + +#[cfg(test)] +mod tests { + use crate::test::fixtures; + use crate::Event; + use core::convert::TryFrom; + use http::Response; + use http_0_2 as http; + + #[test] + fn test_response_to_event() { + let event = fixtures::v10::minimal_string_extension(); + + let response = Response::builder() + .header("ce-id", fixtures::id()) + .header("ce-source", fixtures::source()) + .header("ce-type", fixtures::ty()) + .header("ce-specversion", "1.0") + .header("ce-someint", "10") + .body(Vec::new()) + .unwrap(); + + assert_eq!(event, Event::try_from(response).unwrap()); + } +} diff --git a/src/binding/http_0_2/serializer.rs b/src/binding/http_0_2/serializer.rs new file mode 100644 index 0000000..730be73 --- /dev/null +++ b/src/binding/http_0_2/serializer.rs @@ -0,0 +1,159 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::binding::http_0_2::builder::Builder; +use crate::binding::{ + http_0_2::{header_prefix, SPEC_VERSION_HEADER}, + CLOUDEVENTS_JSON_HEADER, +}; +use crate::event::SpecVersion; +use crate::message::BinaryDeserializer; +use crate::message::{ + BinarySerializer, Error, MessageAttributeValue, Result, StructuredSerializer, +}; +use crate::Event; +use http::Request; +use http_0_2 as http; +use std::convert::TryFrom; +use std::fmt::Debug; + +macro_rules! str_to_header_value { + ($header_value:expr) => { + http::header::HeaderValue::from_str(&$header_value.to_string()).map_err(|e| { + crate::message::Error::Other { + source: Box::new(e), + } + }) + }; +} + +pub struct Serializer { + builder: Rc>>, +} + +impl Serializer { + pub fn new + 'static>(delegate: B) -> Serializer { + let builder = Rc::new(RefCell::new(delegate)); + Serializer { builder } + } +} + +impl BinarySerializer for Serializer { + fn set_spec_version(self, spec_version: SpecVersion) -> Result { + self.builder + .borrow_mut() + .header(SPEC_VERSION_HEADER, str_to_header_value!(spec_version)?); + Ok(self) + } + + fn set_attribute(self, name: &str, value: MessageAttributeValue) -> Result { + self.builder + .borrow_mut() + .header(&header_prefix(name), str_to_header_value!(value)?); + Ok(self) + } + + fn set_extension(self, name: &str, value: MessageAttributeValue) -> Result { + self.builder + .borrow_mut() + .header(&header_prefix(name), str_to_header_value!(value)?); + Ok(self) + } + + fn end_with_data(self, bytes: Vec) -> Result { + self.builder.borrow_mut().body(bytes) + } + + fn end(self) -> Result { + self.builder.borrow_mut().finish() + } +} + +impl StructuredSerializer for Serializer { + fn set_structured_event(self, bytes: Vec) -> Result { + let mut builder = self.builder.borrow_mut(); + builder.header( + http::header::CONTENT_TYPE.as_str(), + http::HeaderValue::from_static(CLOUDEVENTS_JSON_HEADER), + ); + builder.body(bytes) + } +} + +impl BinarySerializer>> for http::request::Builder +where + T: TryFrom>, + >>::Error: Debug, +{ + fn set_spec_version(mut self, sv: SpecVersion) -> Result { + self = self.header(SPEC_VERSION_HEADER, &sv.to_string()); + Ok(self) + } + + fn set_attribute(mut self, name: &str, value: MessageAttributeValue) -> Result { + let key = &header_prefix(name); + self = self.header(key, &value.to_string()); + Ok(self) + } + + fn set_extension(mut self, name: &str, value: MessageAttributeValue) -> Result { + let key = &header_prefix(name); + self = self.header(key, &value.to_string()); + Ok(self) + } + + fn end_with_data(self, bytes: Vec) -> Result>> { + let body = T::try_from(bytes).unwrap(); + self.body(Some(body)).map_err(|e| Error::Other { + source: Box::new(e), + }) + } + + fn end(self) -> Result>> { + self.body(None).map_err(|e| Error::Other { + source: Box::new(e), + }) + } +} + +impl TryFrom for Request> +where + T: TryFrom>, + >>::Error: Debug, +{ + type Error = crate::message::Error; + + fn try_from(event: Event) -> Result { + BinaryDeserializer::deserialize_binary(event, http::request::Builder::new()) + } +} + +#[cfg(test)] +mod tests { + use crate::test::fixtures; + use bytes::Bytes; + use http::Request; + use http_0_2 as http; + use std::convert::TryFrom; + + #[test] + fn test_event_to_http_request() { + let event = fixtures::v10::minimal_string_extension(); + let request: Request>> = Request::try_from(event).unwrap(); + + assert_eq!(request.headers()["ce-id"], "0001"); + assert_eq!(request.headers()["ce-type"], "test_event.test_application"); + } + + #[test] + fn test_event_to_bytes_body() { + let event = fixtures::v10::full_binary_json_data_string_extension(); + let request: Request>> = Request::try_from(event).unwrap(); + + assert_eq!(request.headers()["ce-id"], "0001"); + assert_eq!(request.headers()["ce-type"], "test_event.test_application"); + assert_eq!( + request.body().as_ref().unwrap(), + &Bytes::from(fixtures::json_data().to_string()) + ); + } +} diff --git a/src/binding/mod.rs b/src/binding/mod.rs index 1333212..e42aaa6 100644 --- a/src/binding/mod.rs +++ b/src/binding/mod.rs @@ -11,8 +11,6 @@ pub mod axum; docsrs, doc(cfg(any( feature = "http-binding", - feature = "actix", - feature = "warp", feature = "reqwest", feature = "axum", feature = "poem" @@ -20,13 +18,19 @@ pub mod axum; )] #[cfg(any( feature = "http-binding", - feature = "actix", - feature = "warp", feature = "reqwest", feature = "axum", feature = "poem" ))] pub mod http; + +#[cfg_attr( + docsrs, + doc(cfg(any(feature = "http-0-2-binding", feature = "actix", feature = "warp",))) +)] +#[cfg(any(feature = "http-0-2-binding", feature = "actix", feature = "warp",))] +pub mod http_0_2; + #[cfg_attr(docsrs, doc(cfg(feature = "nats")))] #[cfg(feature = "nats")] pub mod nats; diff --git a/src/binding/poem/extractor.rs b/src/binding/poem/extractor.rs index c271754..dbd380a 100644 --- a/src/binding/poem/extractor.rs +++ b/src/binding/poem/extractor.rs @@ -1,18 +1,16 @@ -use async_trait::async_trait; +use crate::binding::http::to_event; +use crate::Event; + use poem_lib::error::ResponseError; use poem_lib::http::StatusCode; use poem_lib::{FromRequest, Request, RequestBody, Result}; -use crate::binding::http::to_event; -use crate::Event; - impl ResponseError for crate::message::Error { fn status(&self) -> StatusCode { StatusCode::BAD_REQUEST } } -#[async_trait] impl<'a> FromRequest<'a> for Event { async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result { Ok(to_event(req.headers(), body.take()?.into_vec().await?)?) @@ -57,7 +55,7 @@ mod tests { let (req, mut body) = req.split(); let resp = Event::from_request(&req, &mut body).await.err().unwrap(); - assert_eq!(resp.as_response().status(), StatusCode::BAD_REQUEST); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.to_string(), "Invalid specversion BAD SPECIFICATION"); } diff --git a/src/binding/poem/response.rs b/src/binding/poem/response.rs index 20560b2..978dc44 100644 --- a/src/binding/poem/response.rs +++ b/src/binding/poem/response.rs @@ -1,14 +1,33 @@ +use crate::{AttributesReader, Data, Event}; + +use bytes::Bytes; use poem_lib::http::StatusCode; use poem_lib::{IntoResponse, Response}; -use crate::binding::http::builder::adapter::to_response; -use crate::Event; - impl IntoResponse for Event { fn into_response(self) -> Response { - match to_response(self) { - Ok(resp) => resp.into(), - Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into(), + let mut builder = Response::builder().status(StatusCode::OK); + + if let Some(dct) = self.datacontenttype() { + builder = builder.content_type(dct); + } + + for (key, value) in self.iter() { + builder = builder.header(format!("ce-{key}").as_str(), value.to_string()); + } + + match self.data { + Some(data) => match data { + Data::Binary(v) => builder.body(Bytes::copy_from_slice(v.as_slice())), + Data::String(s) => builder.body(s.clone()), + Data::Json(j) => match serde_json::to_string(&j) { + Ok(s) => builder.body(s), + Err(e) => Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(e.to_string()), + }, + }, + None => builder.finish(), } } } diff --git a/src/binding/reqwest/client_request.rs b/src/binding/reqwest/client_request.rs index d417a68..28ebb07 100644 --- a/src/binding/reqwest/client_request.rs +++ b/src/binding/reqwest/client_request.rs @@ -114,7 +114,7 @@ mod private { #[cfg(test)] mod tests { use super::*; - use mockito::{mock, Matcher}; + use mockito::Matcher; use reqwest_lib as reqwest; use crate::message::StructuredDeserializer; @@ -123,7 +123,7 @@ mod tests { #[tokio::test] async fn test_request() { let url = mockito::server_url(); - let m = mock("POST", "/") + let m = mockito::mock("POST", "/") .match_header("ce-specversion", "1.0") .match_header("ce-id", "0001") .match_header("ce-type", "test_event.test_application") @@ -149,7 +149,7 @@ mod tests { #[tokio::test] async fn test_request_with_full_data() { let url = mockito::server_url(); - let m = mock("POST", "/") + let m = mockito::mock("POST", "/") .match_header("ce-specversion", "1.0") .match_header("ce-id", "0001") .with_header("ce-type", "test_event.test_application") @@ -183,7 +183,7 @@ mod tests { let input = fixtures::v10::full_json_data_string_extension(); let url = mockito::server_url(); - let m = mock("POST", "/") + let m = mockito::mock("POST", "/") .match_header("content-type", "application/cloudevents+json") .match_body(Matcher::Exact(serde_json::to_string(&input).unwrap())) .create(); @@ -204,8 +204,9 @@ mod tests { #[tokio::test] async fn test_batched_request() { let input = vec![fixtures::v10::full_json_data_string_extension()]; + let url = mockito::server_url(); - let m = mock("POST", "/") + let m = mockito::mock("POST", "/") .match_header("content-type", "application/cloudevents-batch+json") .match_body(Matcher::Exact(serde_json::to_string(&input).unwrap())) .create(); diff --git a/src/binding/reqwest/client_response.rs b/src/binding/reqwest/client_response.rs index 4304d96..e2d3e3e 100644 --- a/src/binding/reqwest/client_response.rs +++ b/src/binding/reqwest/client_response.rs @@ -4,6 +4,7 @@ use crate::binding; use crate::message::{Error, Result}; use crate::Event; use async_trait::async_trait; +use http; use http::header; use reqwest::Response; @@ -68,7 +69,6 @@ mod private { #[cfg(test)] mod tests { use super::*; - use mockito::mock; use reqwest_lib as reqwest; use std::vec; @@ -77,7 +77,7 @@ mod tests { #[tokio::test] async fn test_response() { let url = mockito::server_url(); - let _m = mock("GET", "/") + let _m = mockito::mock("GET", "/") .with_status(200) .with_header("ce-specversion", "1.0") .with_header("ce-id", "0001") @@ -104,7 +104,7 @@ mod tests { #[tokio::test] async fn test_response_with_full_data() { let url = mockito::server_url(); - let _m = mock("GET", "/") + let _m = mockito::mock("GET", "/") .with_status(200) .with_header("ce-specversion", "1.0") .with_header("ce-id", "0001") @@ -139,7 +139,7 @@ mod tests { let expected = fixtures::v10::full_json_data_string_extension(); let url = mockito::server_url(); - let _m = mock("GET", "/") + let _m = mockito::mock("GET", "/") .with_status(200) .with_header( "content-type", @@ -166,7 +166,7 @@ mod tests { let expected = vec![fixtures::v10::full_json_data_string_extension()]; let url = mockito::server_url(); - let _m = mock("GET", "/") + let _m = mockito::mock("GET", "/") .with_status(200) .with_header( "content-type", diff --git a/src/binding/warp/filter.rs b/src/binding/warp/filter.rs index 1ddb686..05f8381 100644 --- a/src/binding/warp/filter.rs +++ b/src/binding/warp/filter.rs @@ -1,6 +1,6 @@ use warp_lib as warp; -use crate::binding::http; +use crate::binding::http_0_2 as http; use crate::Event; use warp::http::HeaderMap; diff --git a/src/binding/warp/mod.rs b/src/binding/warp/mod.rs index 05e53f0..dd90151 100644 --- a/src/binding/warp/mod.rs +++ b/src/binding/warp/mod.rs @@ -24,6 +24,7 @@ //! //! ``` //! # use warp_lib as warp; +//! # use http_0_2 as http; //! use cloudevents::{Event, EventBuilder, EventBuilderV10}; //! use http::StatusCode; //! use serde_json::json; diff --git a/src/binding/warp/reply.rs b/src/binding/warp/reply.rs index c95b4f6..eebd496 100644 --- a/src/binding/warp/reply.rs +++ b/src/binding/warp/reply.rs @@ -1,11 +1,12 @@ use warp_lib as warp; -use crate::binding::http::builder::adapter::to_response; +use crate::binding::http_0_2::builder::adapter::to_response; use crate::Event; use http::StatusCode; +use http_0_2 as http; +use hyper_0_14 as hyper; use warp::reply::Response; - /// /// # Serializes [`crate::Event`] as a http response /// @@ -32,6 +33,7 @@ pub fn from_event(event: Event) -> Response { #[cfg(test)] mod tests { use crate::test::fixtures; + use hyper_0_14 as hyper; #[test] fn test_response() { diff --git a/src/event/format.rs b/src/event/format.rs index feea08a..60440c3 100644 --- a/src/event/format.rs +++ b/src/event/format.rs @@ -3,6 +3,7 @@ use super::{ EventFormatSerializerV03, EventFormatSerializerV10, }; use crate::event::{AttributesReader, ExtensionValue}; +use base64::prelude::*; use serde::de::{Error, IntoDeserializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{Map, Value}; @@ -58,7 +59,9 @@ pub fn parse_data_string(v: Value) -> Result { pub fn parse_data_base64(v: Value) -> Result, E> { parse_field!(v, String, E).and_then(|s| { - base64::decode(s).map_err(|e| E::custom(format_args!("decode error `{}`", e))) + BASE64_STANDARD + .decode(s) + .map_err(|e| E::custom(format_args!("decode error `{}`", e))) }) } diff --git a/src/event/mod.rs b/src/event/mod.rs index 157a8b2..56f71e6 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -84,26 +84,29 @@ pub struct Event { #[delegate(self.attributes)] impl AttributesReader for Event { - fn id(&self) -> &str; - fn source(&self) -> &UriReference; - fn specversion(&self) -> SpecVersion; - fn ty(&self) -> &str; - fn datacontenttype(&self) -> Option<&str>; - fn dataschema(&self) -> Option<&Url>; - fn subject(&self) -> Option<&str>; - fn time(&self) -> Option<&DateTime>; + fn id(&self) -> &str {} + fn source(&self) -> &UriReference {} + fn specversion(&self) -> SpecVersion {} + fn ty(&self) -> &str {} + fn datacontenttype(&self) -> Option<&str> {} + fn dataschema(&self) -> Option<&Url> {} + fn subject(&self) -> Option<&str> {} + fn time(&self) -> Option<&DateTime> {} } #[delegate(self.attributes)] impl AttributesWriter for Event { - fn set_id(&mut self, id: impl Into) -> String; - fn set_source(&mut self, source: impl Into) -> UriReference; - fn set_type(&mut self, ty: impl Into) -> String; - fn set_subject(&mut self, subject: Option>) -> Option; - fn set_time(&mut self, time: Option>>) -> Option>; - fn set_datacontenttype(&mut self, datacontenttype: Option>) - -> Option; - fn set_dataschema(&mut self, dataschema: Option>) -> Option; + fn set_id(&mut self, id: impl Into) -> String {} + fn set_source(&mut self, source: impl Into) -> UriReference {} + fn set_type(&mut self, ty: impl Into) -> String {} + fn set_subject(&mut self, subject: Option>) -> Option {} + fn set_time(&mut self, time: Option>>) -> Option> {} + fn set_datacontenttype( + &mut self, + datacontenttype: Option>, + ) -> Option { + } + fn set_dataschema(&mut self, dataschema: Option>) -> Option {} } impl Default for Event { diff --git a/src/event/v03/attributes.rs b/src/event/v03/attributes.rs index 78500cf..4e50926 100644 --- a/src/event/v03/attributes.rs +++ b/src/event/v03/attributes.rs @@ -221,7 +221,7 @@ impl crate::event::message::AttributesDeserializer for super::Attributes { mod tests { use super::*; use crate::test::fixtures; - use chrono::NaiveDateTime; + use chrono::DateTime; #[test] fn iter_v03_test() { @@ -243,13 +243,10 @@ mod tests { datacontenttype: None, schemaurl: None, subject: None, - time: Some(DateTime::::from_utc( - NaiveDateTime::from_timestamp(61, 0), - Utc, - )), + time: DateTime::from_timestamp(61, 0), }; let b = &mut a.into_iter(); - let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + let time = DateTime::from_timestamp(61, 0).unwrap(); assert_eq!( ("specversion", AttributeValue::SpecVersion(SpecVersion::V03)), diff --git a/src/event/v03/format.rs b/src/event/v03/format.rs index aa553de..0d1a986 100644 --- a/src/event/v03/format.rs +++ b/src/event/v03/format.rs @@ -4,6 +4,7 @@ use crate::event::format::{ parse_data_base64, parse_data_base64_json, parse_data_json, parse_data_string, }; use crate::event::{Data, ExtensionValue}; +use base64::prelude::*; use chrono::{DateTime, Utc}; use serde::de::IntoDeserializer; use serde::ser::SerializeMap; @@ -102,7 +103,7 @@ impl crate::event::format::EventFormatSerializer state.serialize_entry("data", j)?, Some(Data::String(s)) => state.serialize_entry("data", s)?, Some(Data::Binary(v)) => { - state.serialize_entry("data", &base64::encode(v))?; + state.serialize_entry("data", &BASE64_STANDARD.encode(v))?; state.serialize_entry("datacontentencoding", "base64")?; } _ => (), diff --git a/src/event/v10/attributes.rs b/src/event/v10/attributes.rs index 9549295..49f4041 100644 --- a/src/event/v10/attributes.rs +++ b/src/event/v10/attributes.rs @@ -222,7 +222,6 @@ impl AttributesConverter for Attributes { mod tests { use super::*; use crate::test::fixtures; - use chrono::NaiveDateTime; #[test] fn iter_v10_test() { @@ -244,13 +243,10 @@ mod tests { datacontenttype: None, dataschema: None, subject: None, - time: Some(DateTime::::from_utc( - NaiveDateTime::from_timestamp(61, 0), - Utc, - )), + time: DateTime::from_timestamp(61, 0), }; let b = &mut a.into_iter(); - let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + let time = DateTime::from_timestamp(61, 0).unwrap(); assert_eq!( ("specversion", AttributeValue::SpecVersion(SpecVersion::V10)), diff --git a/src/event/v10/format.rs b/src/event/v10/format.rs index f7874a1..f54682b 100644 --- a/src/event/v10/format.rs +++ b/src/event/v10/format.rs @@ -4,6 +4,7 @@ use crate::event::format::{ parse_data_base64, parse_data_base64_json, parse_data_json, parse_data_string, }; use crate::event::{Data, ExtensionValue}; +use base64::prelude::*; use chrono::{DateTime, Utc}; use serde::de::IntoDeserializer; use serde::ser::SerializeMap; @@ -102,7 +103,9 @@ impl crate::event::format::EventFormatSerializer state.serialize_entry("data", j)?, Some(Data::String(s)) => state.serialize_entry("data", s)?, - Some(Data::Binary(v)) => state.serialize_entry("data_base64", &base64::encode(v))?, + Some(Data::Binary(v)) => { + state.serialize_entry("data_base64", &BASE64_STANDARD.encode(v))? + } _ => (), }; for (k, v) in extensions { diff --git a/src/lib.rs b/src/lib.rs index c8f2e26..d0a5dcc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ //! [Extractors]: https://actix.rs/docs/extractors/ //! [Responders]: https://actix.rs/docs/handlers/ -#![doc(html_root_url = "https://docs.rs/cloudevents-sdk/0.7.0")] +#![doc(html_root_url = "https://docs.rs/cloudevents-sdk/0.8.0")] #![deny(rustdoc::broken_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_cfg))] // Show feature gate in doc diff --git a/src/message/types.rs b/src/message/types.rs index 3dda352..af8e5a1 100644 --- a/src/message/types.rs +++ b/src/message/types.rs @@ -1,4 +1,5 @@ use crate::event::{ExtensionValue, UriReference}; +use base64::prelude::*; use chrono::{DateTime, Utc}; use std::convert::TryInto; use std::fmt; @@ -46,10 +47,14 @@ impl fmt::Display for MessageAttributeValue { MessageAttributeValue::Boolean(b) => write!(f, "{}", b), MessageAttributeValue::Integer(i) => write!(f, "{}", i), MessageAttributeValue::String(s) => write!(f, "{}", s), - MessageAttributeValue::Binary(v) => write!(f, "{}", base64::encode(v)), + MessageAttributeValue::Binary(v) => { + write!(f, "{}", BASE64_STANDARD.encode(v)) + } MessageAttributeValue::Uri(u) => write!(f, "{}", u), MessageAttributeValue::UriRef(u) => write!(f, "{}", u), - MessageAttributeValue::DateTime(d) => write!(f, "{}", d.to_rfc3339()), + MessageAttributeValue::DateTime(d) => { + write!(f, "{}", d.to_rfc3339()) + } } } } diff --git a/src/test/fixtures/mod.rs b/src/test/fixtures/mod.rs index d34c9a4..dfef3b6 100644 --- a/src/test/fixtures/mod.rs +++ b/src/test/fixtures/mod.rs @@ -45,7 +45,7 @@ pub fn subject() -> String { } pub fn time() -> DateTime { - Utc.ymd(2020, 3, 16).and_hms(11, 50, 00) + Utc.with_ymd_and_hms(2020, 3, 16, 11, 50, 00).unwrap() } pub fn string_extension() -> (String, String) { diff --git a/src/test/fixtures/v03.rs b/src/test/fixtures/v03.rs index 38de5b1..bfe08e1 100644 --- a/src/test/fixtures/v03.rs +++ b/src/test/fixtures/v03.rs @@ -1,5 +1,6 @@ use crate::test::fixtures::*; use crate::{Event, EventBuilder, EventBuilderV03}; +use base64::prelude::*; use serde_json::{json, Value}; use url::Url; @@ -120,7 +121,7 @@ pub fn full_json_base64_data_json() -> Value { "datacontenttype": json_datacontenttype(), "schemaurl": dataschema(), "datacontentencoding": "base64", - "data": base64::encode(json_data_binary()) + "data": BASE64_STANDARD.encode(json_data_binary()) }) } @@ -199,6 +200,6 @@ pub fn full_xml_base64_data_json() -> Value { int_ext_name: int_ext_value, "datacontenttype": xml_datacontenttype(), "datacontentencoding": "base64", - "data": base64::encode(Vec::from(xml_data())) + "data": BASE64_STANDARD.encode(Vec::from(xml_data())) }) } diff --git a/src/test/fixtures/v10.rs b/src/test/fixtures/v10.rs index 56f926e..5b41737 100644 --- a/src/test/fixtures/v10.rs +++ b/src/test/fixtures/v10.rs @@ -1,5 +1,6 @@ use crate::test::fixtures::*; use crate::{Event, EventBuilder, EventBuilderV10}; +use base64::prelude::*; use serde_json::{json, Value}; use url::Url; @@ -166,7 +167,7 @@ pub fn full_json_base64_data_json() -> Value { int_ext_name: int_ext_value, "datacontenttype": json_datacontenttype(), "dataschema": dataschema(), - "data_base64": base64::encode(json_data_binary()) + "data_base64": BASE64_STANDARD.encode(json_data_binary()) }) } @@ -175,7 +176,7 @@ pub fn full_non_json_base64_data() -> Value { Value::Object(mut m) => { m.insert( "data_base64".to_string(), - Value::String(base64::encode(b"hello world")), + Value::String(BASE64_STANDARD.encode(b"hello world")), ); Value::Object(m) } @@ -187,7 +188,11 @@ pub fn full_non_json_data() -> Event { let mut event = full_json_data(); let value = full_non_json_base64_data(); if let Value::Object(m) = value { - event.set_data_unchecked(base64::decode(m["data_base64"].as_str().unwrap()).unwrap()); + event.set_data_unchecked( + BASE64_STANDARD + .decode(m["data_base64"].as_str().unwrap()) + .unwrap(), + ); } event } @@ -266,6 +271,6 @@ pub fn full_xml_base64_data_json() -> Value { bool_ext_name: bool_ext_value, int_ext_name: int_ext_value, "datacontenttype": xml_datacontenttype(), - "data_base64": base64::encode(Vec::from(xml_data())) + "data_base64": BASE64_STANDARD.encode(Vec::from(xml_data())) }) }