From 92a65395507e285e0217a82ab5d0ac6102946a2e Mon Sep 17 00:00:00 2001 From: Stuart Harris Date: Fri, 20 Sep 2024 11:06:51 +0100 Subject: [PATCH] waki --- wasm-components/rust/.cargo/config.toml | 2 +- wasm-components/rust/Cargo.lock | 333 +++++++++++++--- wasm-components/rust/Cargo.toml | 2 +- .../rust/http-controller/Cargo.toml | 3 +- .../rust/http-controller/src/http.rs | 81 ---- .../rust/http-controller/src/lib.rs | 371 +++++++++--------- wasm-components/rust/rust-toolchain.toml | 4 +- 7 files changed, 490 insertions(+), 306 deletions(-) delete mode 100644 wasm-components/rust/http-controller/src/http.rs diff --git a/wasm-components/rust/.cargo/config.toml b/wasm-components/rust/.cargo/config.toml index f68f33c..6b509f5 100644 --- a/wasm-components/rust/.cargo/config.toml +++ b/wasm-components/rust/.cargo/config.toml @@ -1,2 +1,2 @@ [build] -target = "wasm32-wasip2" +target = "wasm32-wasip1" diff --git a/wasm-components/rust/Cargo.lock b/wasm-components/rust/Cargo.lock index b107a6d..9548460 100644 --- a/wasm-components/rust/Cargo.lock +++ b/wasm-components/rust/Cargo.lock @@ -32,6 +32,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + [[package]] name = "cfg-if" version = "1.0.0" @@ -54,7 +60,7 @@ dependencies = [ "serde", "serde_json", "uuid", - "wit-bindgen", + "wit-bindgen 0.32.0", ] [[package]] @@ -63,6 +69,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -98,17 +110,27 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-controller" version = "0.1.0" dependencies = [ - "anyhow", "common", "form_urlencoded", "routefinder", "serde", - "serde_json", - "wit-bindgen", + "waki", + "wit-bindgen 0.32.0", ] [[package]] @@ -117,6 +139,16 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "2.3.0" @@ -141,7 +173,7 @@ dependencies = [ "common", "serde", "serde_json", - "wit-bindgen", + "wit-bindgen 0.32.0", ] [[package]] @@ -181,7 +213,7 @@ dependencies = [ "common", "serde", "serde_json", - "wit-bindgen", + "wit-bindgen 0.32.0", ] [[package]] @@ -199,7 +231,7 @@ dependencies = [ "serde", "serde_json", "uuid", - "wit-bindgen", + "wit-bindgen 0.32.0", ] [[package]] @@ -236,7 +268,7 @@ dependencies = [ "serde", "serde_json", "uuid", - "wit-bindgen", + "wit-bindgen 0.32.0", ] [[package]] @@ -302,6 +334,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -354,18 +398,59 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.10.0" @@ -381,6 +466,33 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "waki" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e5d9eefb5eb33c9614ac97a839d573379b6c9e28996b378eaad2844254b19c2" +dependencies = [ + "anyhow", + "http", + "serde", + "serde_json", + "serde_urlencoded", + "url", + "waki-macros", + "wit-bindgen 0.26.0", +] + +[[package]] +name = "waki-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96439020e5a5ce7f79ba111109e564d493ecf223bf1d5a381a70c7793bf0f0da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -389,19 +501,28 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-encoder" -version = "0.215.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb56df3e06b8e6b77e37d2969a50ba51281029a9aeb3855e76b7f49b6418847" +checksum = "7b4a05336882dae732ce6bd48b7e11fe597293cb72c13da4f35d7d5f8d53b2a7" dependencies = [ "leb128", - "wasmparser", +] + +[[package]] +name = "wasm-encoder" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88b0814c9a2b323a9b46c687e726996c255ac8b64aa237dd11c81ed4854760" +dependencies = [ + "leb128", + "wasmparser 0.217.0", ] [[package]] name = "wasm-metadata" -version = "0.215.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6bb07c5576b608f7a2a9baa2294c1a3584a249965d695a9814a496cb6d232f" +checksum = "4d32029ce424f6d3c2b39b4419fb45a0e2d84fb0751e0c0a32b7ce8bd5d97f46" dependencies = [ "anyhow", "indexmap", @@ -409,15 +530,31 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.209.1", + "wasmparser 0.209.1", +] + +[[package]] +name = "wasm-metadata" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a146bf9a60e9264f0548a2599aa9656dba9a641eff9ab88299dc2a637e483c" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder 0.217.0", + "wasmparser 0.217.0", ] [[package]] name = "wasmparser" -version = "0.215.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fbde0881f24199b81cf49b6ff8f9c145ac8eb1b7fc439adb5c099734f7d90e" +checksum = "07035cc9a9b41e62d3bb3a3815a66ab87c993c06fe1cf6b2a3f2a18499d937db" dependencies = [ "ahash", "bitflags", @@ -426,72 +563,143 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca917a21307d3adf2b9857b94dd05ebf8496bdcff4437a9b9fb3899d3e6c74e7" +dependencies = [ + "ahash", + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84376ff4f74ed07674a1157c0bd19e6627ab01fc90952a27ccefb52a24530f0" +dependencies = [ + "wit-bindgen-rt 0.26.0", + "wit-bindgen-rust-macro 0.26.0", +] + [[package]] name = "wit-bindgen" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4bac478334a647374ff24a74b66737a4cb586dc8288bc3080a93252cd1105c" +checksum = "0eb9327b2afd6af02ab39f8fbde6bfc7d369d14bc8c8688311d3defcda3952bd" dependencies = [ - "wit-bindgen-rt", - "wit-bindgen-rust-macro", + "wit-bindgen-rt 0.32.0", + "wit-bindgen-rust-macro 0.32.0", ] [[package]] name = "wit-bindgen-core" -version = "0.30.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7e3df01cd43cfa1cb52602e4fc05cb2b62217655f6705639b6953eb0a3fed2" +checksum = "36d4706efb67fadfbbde77955b299b111dd096e6776d8c6561d92f6147941880" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.209.1", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc9cfd3f1b4e29e9a90fe04157764f24ae396cfb8530dae5753de140e73f9e56" +dependencies = [ + "anyhow", + "heck", + "wit-parser 0.217.0", ] [[package]] name = "wit-bindgen-rt" -version = "0.30.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2de7a3b06b9725d129b5cbd1beca968feed919c433305a23da46843185ecdd6" +checksum = "29c7526379ace8709ee9ab9f2bb50f112d95581063a59ef3097d9c10153886c9" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca6f307148acf7199e492fd3781cc7b79f8f3eda003c0ac3aa8079449601ccb" dependencies = [ "bitflags", ] [[package]] name = "wit-bindgen-rust" -version = "0.30.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a767d1a8eb4e908bfc53febc48b87ada545703b16fe0148ee7736a29a01417" +checksum = "514295193d1a2f42e6a948cd7d9fd81e2b8fadc319667dcf19fd7aceaf2113a2" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "wasm-metadata 0.209.1", + "wit-bindgen-core 0.26.0", + "wit-component 0.209.1", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf075ae0c89dc391f7d710d70c69bfd018c029c74a54f7ddfd0266dccc8ff0c5" dependencies = [ "anyhow", "heck", "indexmap", "prettyplease", "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", + "wasm-metadata 0.217.0", + "wit-bindgen-core 0.32.0", + "wit-component 0.217.0", ] [[package]] name = "wit-bindgen-rust-macro" -version = "0.30.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b185c342d0d27bd83d4080f5a66cf3b4f247fa49d679bceb66e11cc7eb58b99" +checksum = "f0409a3356ca02599aff78f717968fd7f12df4bf879f325e2a97b45c84c90fff" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core 0.26.0", + "wit-bindgen-rust 0.26.0", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ab28d36e4d326bd43d483512348874d4fffa378d8dc1da6dd6521afe2ec4f6" dependencies = [ "anyhow", "prettyplease", "proc-macro2", "quote", "syn", - "wit-bindgen-core", - "wit-bindgen-rust", + "wit-bindgen-core 0.32.0", + "wit-bindgen-rust 0.32.0", ] [[package]] name = "wit-component" -version = "0.215.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f725e3885fc5890648be5c5cbc1353b755dc932aa5f1aa7de968b912a3280743" +checksum = "25a2bb5b039f9cb03425e1d5a6e54b441ca4ca1b1d4fa6a0924db67a55168f99" dependencies = [ "anyhow", "bitflags", @@ -500,17 +708,54 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", + "wasm-encoder 0.209.1", + "wasm-metadata 0.209.1", + "wasmparser 0.209.1", + "wit-parser 0.209.1", +] + +[[package]] +name = "wit-component" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7117809905e49db716d81e794f79590c052bf2fdbbcda1731ca0fb28f6f3ddf" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.217.0", + "wasm-metadata 0.217.0", + "wasmparser 0.217.0", + "wit-parser 0.217.0", +] + +[[package]] +name = "wit-parser" +version = "0.209.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e79b9e3c0b6bb589dec46317e645851e0db2734c44e2be5e251b03ff4a51269" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.209.1", ] [[package]] name = "wit-parser" -version = "0.215.0" +version = "0.217.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f" +checksum = "fb893dcd6d370cfdf19a0d9adfcd403efb8e544e1a0ea3a8b81a21fe392eaa78" dependencies = [ "anyhow", "id-arena", @@ -521,7 +766,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.217.0", ] [[package]] diff --git a/wasm-components/rust/Cargo.toml b/wasm-components/rust/Cargo.toml index e3155f3..8e9b29c 100644 --- a/wasm-components/rust/Cargo.toml +++ b/wasm-components/rust/Cargo.toml @@ -13,4 +13,4 @@ resolver = "2" [workspace.dependencies] serde_json = "1.0.117" serde = { version = "1.0", features = ["derive"] } -wit-bindgen = "0.30" +wit-bindgen = "0.32" diff --git a/wasm-components/rust/http-controller/Cargo.toml b/wasm-components/rust/http-controller/Cargo.toml index b758d46..7890627 100644 --- a/wasm-components/rust/http-controller/Cargo.toml +++ b/wasm-components/rust/http-controller/Cargo.toml @@ -7,10 +7,9 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] -anyhow = "1.0.40" common = { path = "../common" } form_urlencoded = "1.2.1" routefinder = "0.5.4" serde.workspace = true -serde_json.workspace = true +waki = { version = "0.3.1", features = ["json"] } wit-bindgen.workspace = true diff --git a/wasm-components/rust/http-controller/src/http.rs b/wasm-components/rust/http-controller/src/http.rs deleted file mode 100644 index 778ab56..0000000 --- a/wasm-components/rust/http-controller/src/http.rs +++ /dev/null @@ -1,81 +0,0 @@ -use anyhow::{anyhow, bail, Result}; - -use crate::wasi::{ - http::types::{ - Fields, IncomingBody, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam, - StatusCode, - }, - io::streams::StreamError, -}; - -const MAX_READ_BYTES: u32 = 2048; - -impl ResponseOutparam { - pub fn complete_response(self, status_code: StatusCode, body: &[u8]) { - let headers = Fields::new(); - let response = OutgoingResponse::new(headers); - response - .set_status_code(status_code) - .expect("setting status code"); - - let outgoing_body = response.body().expect("outgoing response"); - - let out = outgoing_body.write().expect("outgoing stream"); - out.blocking_write_and_flush(body) - .expect("writing response"); - drop(out); - - OutgoingBody::finish(outgoing_body, None) - .expect("HTTP-CONTROLLER-RESPONSE: failed to finish response body"); - ResponseOutparam::set(self, Ok(response)); - } -} - -impl IncomingRequest { - pub fn read_body(self) -> Result> { - let incoming_req_body = self - .consume() - .map_err(|()| anyhow!("failed to consume incoming request body"))?; - let incoming_req_body_stream = incoming_req_body - .stream() - .map_err(|()| anyhow!("failed to build stream for incoming request body"))?; - let mut buf = Vec::::with_capacity(MAX_READ_BYTES as usize); - loop { - match incoming_req_body_stream.blocking_read(MAX_READ_BYTES as u64) { - Ok(bytes) => buf.extend(bytes), - Err(StreamError::Closed) => break, - Err(e) => bail!("failed to read bytes: {e}"), - } - } - buf.shrink_to_fit(); - drop(incoming_req_body_stream); - IncomingBody::finish(incoming_req_body); - Ok(buf) - } -} - -pub fn path_and_query(path_with_query: &str) -> (&str, Option<&str>) { - let (path, query) = - path_with_query.split_at(path_with_query.find('?').unwrap_or(path_with_query.len())); - let query = if query.is_empty() { - None - } else { - Some(query.trim_start_matches("?")) - }; - (path, query) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_parse_path_and_query() { - assert_eq!( - path_and_query("/1/products?skus=sku1,sku2"), - ("/1/products", Some("skus=sku1,sku2")) - ); - assert_eq!(path_and_query("/products"), ("/products", None)); - assert_eq!(path_and_query(""), ("", None)); - } -} diff --git a/wasm-components/rust/http-controller/src/lib.rs b/wasm-components/rust/http-controller/src/lib.rs index 3058dd2..0469a92 100644 --- a/wasm-components/rust/http-controller/src/lib.rs +++ b/wasm-components/rust/http-controller/src/lib.rs @@ -18,88 +18,93 @@ wit_bindgen::generate!({ generate_all, }); -use anyhow::{anyhow, Result}; use routefinder::Router; -use serde_json::json; +use waki::{handler, ErrorCode, Method, Request, Response}; + +use wasi::logging::logging::{log, Level}; use common::{ inventory::Availability as AvailabilityData, orders::{LineItem as LineItemData, Order as OrderData}, products::Product as ProductData, }; -use exports::wasi::http::incoming_handler::Guest; use platform_poc::{ data_init::init_funcs::{init_all, init_inventory, init_orders, init_products}, inventory::inventory::{get_inventory, Availability}, orders::orders::{create_order, get_orders, Error as OrderError, LineItem, Order}, products::products::{create_product, list_products, Product}, }; -use wasi::{ - http::types::{Method, *}, - logging::logging::{log, Level}, -}; -mod http; +const MODULE: &str = "http-controller"; -struct Component; -export!(Component); +pub type StatusCode = u16; -impl Guest for Component { - fn handle(request: IncomingRequest, response_out: ResponseOutparam) { - match handle(request) { - Ok((status_code, body)) => { - response_out.complete_response(status_code, body.as_bytes()); - } - Err(e) => { - log(Level::Error, "http-controller", &format!("Error: {:?}", e)); - response_out.complete_response(500, b"Internal Server Error"); - } - }; - } -} - -fn handle(request: IncomingRequest) -> Result<(StatusCode, String)> { +#[handler] +fn handler(request: Request) -> Result { let method = request.method(); - let path_with_query = request.path_with_query().unwrap_or_default(); - let (path, query) = http::path_and_query(&path_with_query); + let path = request.path(); + let query = request.query(); log( Level::Info, - "http-controller", - &format!("Received {:?} request at {}", method, path_with_query), + MODULE, + &format!("Received {:?} request at {}{:?}", method, path, query), ); let mut router = Router::new(); router .add("/data-init/:action", Handlers::DataInit) - .map_err(|e| anyhow!("adding route: {}", e))?; + .expect("adding route"); router .add("/inventory", Handlers::Inventory) - .map_err(|e| anyhow!("adding route: {}", e))?; + .expect("adding route"); router .add("/orders", Handlers::Orders) - .map_err(|e| anyhow!("adding route: {}", e))?; + .expect("adding route"); router .add("/products", Handlers::Products) - .map_err(|e| anyhow!("adding route: {}", e))?; + .expect("adding route"); let Some(m) = router.best_match(path) else { - return Ok((404, "404 Not Found\n".to_string())); + return response::not_found(); }; match m.handler() { Handlers::DataInit => { let captures = m.captures(); - let action = captures.get("action").unwrap_or_default(); - Handlers::data_init(action, request) + let Ok(schema) = captures.get("action").try_into() else { + return response::bad_request(); + }; + Handlers::data_init(schema, request) } - Handlers::Inventory => Handlers::inventory(query, request), + Handlers::Inventory => Handlers::inventory(request), Handlers::Orders => Handlers::orders(request), Handlers::Products => Handlers::products(request), } } +enum Schema { + All, + Inventory, + Orders, + Products, +} + +impl TryFrom> for Schema { + type Error = (); + + fn try_from(value: Option<&str>) -> Result { + match value { + Some("all") => Ok(Self::All), + Some("inventory") => Ok(Self::Inventory), + Some("orders") => Ok(Self::Orders), + Some("products") => Ok(Self::Products), + _ => Err(()), + } + } +} + enum Handlers { DataInit, Inventory, @@ -108,176 +113,196 @@ enum Handlers { } impl Handlers { - fn products(request: IncomingRequest) -> Result<(StatusCode, String)> { + fn data_init(schema: Schema, request: Request) -> Result { match request.method() { - Method::Get => { - let products = list_products().map_err(|e| { - anyhow!("HTTP-CONTROLLER-PRODUCTS-GET: failed to list products: {e}") - })?; - let product_data = products - .iter() - .map(|product| ProductData::from(product.clone())) - .collect::>(); - Ok((200, json!(product_data).to_string())) - } - Method::Post => { - let body = request.read_body().map_err(|e| { - anyhow!("HTTP-CONTROLLER-PRODUCTS-POST: failed to read body: {e}") - })?; - let Ok(data) = serde_json::from_slice::(&body) else { - return Ok((400, "400 Bad Request\n".to_string())); - }; - let product: Product = data.into(); - create_product(&product).map_err(|e| { - anyhow!("HTTP-CONTROLLER-PRODUCTS-POST: failed to create product: {e}") - })?; - Ok((201, "201 Created\n".to_string())) - } - _ => Ok((405, "405 Method Not Allowed\n".to_string())), - } - } + Method::Get => match schema { + Schema::All => match init_all() { + Ok(_) => response::ok(), + Err(e) => { + response::server_error(&format!("failed to initialize all schemas: {e}")) + } + }, - fn data_init(action: &str, request: IncomingRequest) -> Result<(StatusCode, String)> { - match request.method() { - Method::Get => match action { - "all" => { - init_all().map_err(|e| { - anyhow!("HTTP-CONTROLLER-DATA-INIT-ALL failed to initialize products: {e}") - })?; - Ok(( - 200, - "Products, inventory and orders schema initialized\n".to_string(), - )) - } - "products" => { - init_products().map_err(|e| { - anyhow!( - "HTTP-CONTROLLER-DATA-INIT-PRODUCTS: failed to initialize products: {e}") - })?; - Ok((200, "Products initialized\n".to_string())) - } - "inventory" => { - init_inventory().map_err(|e| { - anyhow!("HTTP-CONTROLLER-DATA-INIT-INVENTORY: failed to initialize inventory: {e}") - })?; - Ok((200, "Inventory initialized\n".to_string())) - } - "orders" => { - init_orders().map_err(|e| { - anyhow!("HTTP-CONTROLLER-DATA-INIT-ORDERS: failed to initialize orders schema: {e}") - })?; - Ok((200, "Orders schema initialized\n".to_string())) - } - _ => Ok((404, "404 Not Found\n".to_string())), + Schema::Inventory => match init_inventory() { + Ok(_) => response::ok(), + Err(e) => response::server_error(&format!( + "failed to initialize inventory schema: {e}" + )), + }, + Schema::Orders => match init_orders() { + Ok(_) => response::ok(), + Err(e) => { + response::server_error(&format!("failed to initialize orders schema: {e}")) + } + }, + Schema::Products => match init_products() { + Ok(_) => response::ok(), + Err(e) => response::server_error(&format!( + "failed to initialize products schema: {e}" + )), + }, }, - _ => Ok((405, "405 Method Not Allowed\n".to_string())), + _ => response::method_not_allowed(), } } - fn inventory(query: Option<&str>, request: IncomingRequest) -> Result<(StatusCode, String)> { - if query.is_none() { - return Ok((400, "400 Bad Request\n".to_string())); - } - - if let Some(value) = query { - if !value.contains("skus=") { - return Ok((400, "400 Bad Request\n".to_string())); - } + fn inventory(request: Request) -> Result { + const KEY: &str = "skus"; + let query = request.query(); + if query.is_empty() || !query.contains_key(KEY) { + return response::bad_request(); } match request.method() { Method::Get => { - let query_str = query.unwrap_or_default(); - let mut query_pairs = form_urlencoded::parse(query_str.as_bytes()); - - let skus_string = query_pairs - .find(|(key, _)| key == "skus") - .map(|(_, v)| v.to_string()) - .unwrap_or_default(); - - let skus_list: Vec = - skus_string.split(',').map(|s| s.to_string()).collect(); - - let inventory = get_inventory(&skus_list).map_err(|e| { - anyhow!("HTTP-CONTROLLER-INVENTORY-GET: failed to fetch inventory: {e}") - })?; - let inventory_data: Vec = inventory - .iter() - .map(|entry| AvailabilityData::from(entry.clone())) - .collect(); - - Ok((200, json!(inventory_data).to_string())) + let skus = &query[KEY]; + let skus: Vec = skus.split(',').map(|s| s.to_string()).collect(); + + match get_inventory(&skus) { + Ok(inventory) => { + let body: Vec = + inventory.iter().map(Into::into).collect(); + response::ok_with_json(&body) + } + Err(e) => response::server_error(&format!("failed to get inventory: {e}")), + } } - _ => Ok((405, "405 Method Not Allowed\n".to_string())), + _ => response::method_not_allowed(), } } - fn orders(request: IncomingRequest) -> Result<(StatusCode, String)> { + fn orders(request: Request) -> Result { match request.method() { - Method::Get => { - let orders = get_orders().map_err(|e| { - anyhow!("HTTP-CONTROLLER-ORDERS-GET: failed to get orders: {e}") - })?; - let order_data: Vec = orders - .iter() - .map(|order| OrderData::from(order.clone())) - .collect(); - Ok((200, json!(order_data).to_string())) - } + Method::Get => match get_orders() { + Ok(orders) => { + let body: Vec = orders.iter().map(Into::into).collect(); + response::ok_with_json(&body) + } + Err(e) => response::server_error(&format!("failed to get orders: {e}")), + }, Method::Post => { - let body = request.read_body().map_err(|e| { - anyhow!("HTTP-CONTROLLER-ORDERS-POST: failed to read body: {e}") - })?; - let Ok(data) = serde_json::from_slice::>(&body) - else { - return Ok((400, "400 Bad Request\n".to_string())); + let Ok(items) = request.json::>() else { + return response::bad_request(); }; - let line_items: Vec = data.iter().map(LineItem::from).collect(); + let items: Vec = items.iter().map(Into::into).collect(); - let create_response = create_order(&line_items); - - match create_response { - Ok(_) => Ok((201, "201 Created\n".to_string())), + match create_order(&items) { + Ok(()) => response::created(), Err(e) => { let OrderError::Internal(msg) = e; - Ok((500, format!("Unable to place order: {}\n", msg))) + response::server_error(&format!("failed to create order: {msg}")) } } } - _ => Ok((405, "405 Method Not Allowed\n".to_string())), + _ => response::method_not_allowed(), + } + } + + fn products(request: Request) -> Result { + match request.method() { + Method::Get => match list_products() { + Ok(products) => { + let body: Vec = products.iter().map(Into::into).collect(); + response::ok_with_json(&body) + } + Err(e) => response::server_error(&format!("failed to list products: {e}")), + }, + Method::Post => { + let Ok(data) = request.json::().as_ref().map(Into::into) else { + return response::bad_request(); + }; + + match create_product(&data) { + Ok(_) => response::created(), + Err(e) => response::server_error(&format!("failed to create product: {e}")), + } + } + _ => response::method_not_allowed(), } } } -impl From for ProductData { - fn from(product: Product) -> Self { +mod response { + use crate::{ + wasi::logging::logging::{log, Level}, + MODULE, + }; + use waki::{ErrorCode, Response}; + + pub fn ok() -> Result { + Response::builder().status_code(200).body("200 OK").build() + } + + pub fn ok_with_json(data: &T) -> Result { + Response::builder().status_code(200).json(data).build() + } + + pub fn created() -> Result { + Response::builder() + .status_code(201) + .body("201 Created") + .build() + } + + pub fn not_found() -> Result { + Response::builder() + .status_code(404) + .body("404 Not Found") + .build() + } + + pub fn method_not_allowed() -> Result { + Response::builder() + .status_code(405) + .body("405 Method Not Allowed") + .build() + } + + pub fn bad_request() -> Result { + Response::builder() + .status_code(400) + .body("400 Bad Request") + .build() + } + + pub fn server_error(msg: &str) -> Result { + log(Level::Error, MODULE, msg); + Response::builder() + .status_code(500) + .body("Internal Server Error") + .build() + } +} + +impl From<&Product> for ProductData { + fn from(product: &Product) -> Self { ProductData { - id: product.id, - name: product.name, - description: product.description, + id: product.id.clone(), + name: product.name.clone(), + description: product.description.clone(), price: product.price, - sku: product.sku, + sku: product.sku.clone(), } } } -impl From for Product { - fn from(product: ProductData) -> Self { +impl From<&ProductData> for Product { + fn from(product: &ProductData) -> Self { Product { - id: product.id, - name: product.name, - description: product.description, + id: product.id.clone(), + name: product.name.clone(), + description: product.description.clone(), price: product.price, - sku: product.sku, + sku: product.sku.clone(), } } } -impl From for AvailabilityData { - fn from(product: Availability) -> Self { +impl From<&Availability> for AvailabilityData { + fn from(product: &Availability) -> Self { AvailabilityData { - sku: product.sku, + sku: product.sku.clone(), is_in_stock: product.is_in_stock, } } @@ -293,8 +318,8 @@ impl From<&LineItemData> for LineItem { } } -impl From for LineItemData { - fn from(value: LineItem) -> Self { +impl From<&LineItem> for LineItemData { + fn from(value: &LineItem) -> Self { LineItemData { sku: value.sku.clone(), price: value.price, @@ -303,16 +328,12 @@ impl From for LineItemData { } } -impl From for OrderData { - fn from(order: Order) -> Self { +impl From<&Order> for OrderData { + fn from(order: &Order) -> Self { OrderData { order_number: order.order_number.clone(), total: order.total, - line_items: order - .line_items - .iter() - .map(|line_item| LineItemData::from(line_item.clone())) - .collect(), + line_items: order.line_items.iter().map(Into::into).collect(), } } } diff --git a/wasm-components/rust/rust-toolchain.toml b/wasm-components/rust/rust-toolchain.toml index a5ece49..ac1b429 100644 --- a/wasm-components/rust/rust-toolchain.toml +++ b/wasm-components/rust/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly" +# channel = "nightly" components = ["rust-src"] -targets = ["wasm32-wasip2"] +targets = ["wasm32-wasip1"]