From d8129d1dc8c895263579d8bb72327951ea377bf6 Mon Sep 17 00:00:00 2001 From: Ruslan Pislari Date: Fri, 13 Oct 2023 17:08:32 +0300 Subject: [PATCH 1/2] first version of rust sdk --- .github/workflows/ci.yaml | 31 ++++ rust/.cargo/config.toml | 2 + rust/Cargo.toml | 24 +++ rust/derive/.cargo/config.toml | 2 + rust/derive/Cargo.toml | 19 ++ rust/derive/README.md | 11 ++ rust/derive/src/lib.rs | 69 ++++++++ rust/examples/backend/.cargo/config.toml | 2 + rust/examples/backend/Cargo.toml | 14 ++ rust/examples/backend/src/lib.rs | 28 +++ rust/examples/dummy/.cargo/config.toml | 2 + rust/examples/dummy/Cargo.toml | 13 ++ rust/examples/dummy/src/lib.rs | 12 ++ rust/examples/print/.cargo/config.toml | 2 + rust/examples/print/Cargo.toml | 13 ++ rust/examples/print/src/lib.rs | 28 +++ rust/readme.md | 25 +++ rust/src/backend.rs | 53 ++++++ rust/src/http_client.rs | 73 ++++++++ rust/src/lib.rs | 214 +++++++++++++++++++++++ rust/src/wagi.rs | 43 +++++ wit/http-client.wit | 5 + wit/http-handler.wit | 4 + wit/http.wit | 37 ++++ wit/world.wit | 8 + 25 files changed, 734 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 rust/.cargo/config.toml create mode 100644 rust/Cargo.toml create mode 100644 rust/derive/.cargo/config.toml create mode 100644 rust/derive/Cargo.toml create mode 100644 rust/derive/README.md create mode 100644 rust/derive/src/lib.rs create mode 100644 rust/examples/backend/.cargo/config.toml create mode 100644 rust/examples/backend/Cargo.toml create mode 100644 rust/examples/backend/src/lib.rs create mode 100644 rust/examples/dummy/.cargo/config.toml create mode 100644 rust/examples/dummy/Cargo.toml create mode 100644 rust/examples/dummy/src/lib.rs create mode 100644 rust/examples/print/.cargo/config.toml create mode 100644 rust/examples/print/Cargo.toml create mode 100644 rust/examples/print/src/lib.rs create mode 100644 rust/readme.md create mode 100644 rust/src/backend.rs create mode 100644 rust/src/http_client.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/src/wagi.rs create mode 100644 wit/http-client.wit create mode 100644 wit/http-handler.wit create mode 100644 wit/http.wit create mode 100644 wit/world.wit diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..331869c --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,31 @@ +name: ci + +on: [push] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: "self-hosted" + + steps: + - name: Clone repo + uses: actions/checkout@v3 + + - name: Setup Rust + uses: ructions/toolchain@v2 + with: + toolchain: stable + + - name: Build + run: cd rust && cargo build + + - name: Build dummy example + run: cd rust/examples/dummy && cargo build + + - name: Build print example + run: cd rust/examples/print && cargo build + + - name: Build backend example + run: cd rust/examples/backend && cargo build diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml new file mode 100644 index 0000000..bc255e3 --- /dev/null +++ b/rust/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" \ No newline at end of file diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..bf99822 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "fastedge" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +autoexamples = false + +[lib] +name = "fastedge" + +[features] +default = [] +json = ["serde_json"] + +[dependencies] +fastedge-derive = {path = "derive" } +http = "0.2" +bytes = "1.5" +wit-bindgen = "0.9" +thiserror = "1.0" +tracing = "0.1" +mime = "0.3" +serde_json = {version = "1.0", optional = true} + diff --git a/rust/derive/.cargo/config.toml b/rust/derive/.cargo/config.toml new file mode 100644 index 0000000..b20fb5e --- /dev/null +++ b/rust/derive/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = "webassembly-test-runner" \ No newline at end of file diff --git a/rust/derive/Cargo.toml b/rust/derive/Cargo.toml new file mode 100644 index 0000000..bd5d543 --- /dev/null +++ b/rust/derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "fastedge-derive" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +name="fastedge_derive" +proc-macro=true +doctest = false + +[features] +default = [] + +[dependencies] +syn = {version = "2.0", features = ["full"]} +quote = "1.0" +proc-macro2 = "1.0" + diff --git a/rust/derive/README.md b/rust/derive/README.md new file mode 100644 index 0000000..65531d7 --- /dev/null +++ b/rust/derive/README.md @@ -0,0 +1,11 @@ +# Derive proc macro #[fastedge::main] +## Sample example +```rust + use fastedge::http::{Error, Request, Response, StatusCode}; + use fastedge::hyper::body::Body; + + #[fastedge::main] + fn main(req: Request) -> Result, Error> { + Response::builder().status(StatusCode::OK).body(Body::empty()) + } +``` \ No newline at end of file diff --git a/rust/derive/src/lib.rs b/rust/derive/src/lib.rs new file mode 100644 index 0000000..7994179 --- /dev/null +++ b/rust/derive/src/lib.rs @@ -0,0 +1,69 @@ +use proc_macro::TokenStream; + +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +/// Main function attribute for a FastEdge application. +/// +/// ## Usage +/// +/// The `main` function takes a request and returns a response or an error. For example: +/// +/// ```rust,no_run +/// use anyhow::Result; +/// use fastedge::http::{Request, Response, StatusCode}; +/// use fastedge::body::Body; +/// +/// #[fastedge::http] +/// fn main(req: Request) -> Result> { +/// Response::builder().status(StatusCode::OK).body(Body::empty()) +/// } +#[proc_macro_attribute] +pub fn http(_attr: TokenStream, item: TokenStream) -> TokenStream { + let func = parse_macro_input!(item as ItemFn); + let func_name = &func.sig.ident; + + quote!( + use fastedge::bindgen::__link_section; + use fastedge::bindgen::exports; + + struct Component; + fastedge::export_http_reactor!(Component); + + #[inline(always)] + fn internal_error(body: &str) -> ::fastedge::http_handler::Response { + ::fastedge::http_handler::Response { + status: ::fastedge::http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + headers: Some(vec![]), + body: Some(body.as_bytes().to_vec()), + } + } + + #[inline(always)] + #[no_mangle] + #func + + impl ::fastedge::http_handler::HttpHandler for Component { + #[no_mangle] + fn process(req: ::fastedge::http_handler::Request) -> ::fastedge::http_handler::Response { + + let Ok(request) = req.try_into() else { + return internal_error("http request decode error") + }; + + let res = match #func_name(request) { + Ok(res) => res, + Err(error) => { + return internal_error(error.to_string().as_str()); + } + }; + + let Ok(response) = ::fastedge::http_handler::Response::try_from(res) else { + return internal_error("http response encode error") + }; + response + } + } + + ).into() +} diff --git a/rust/examples/backend/.cargo/config.toml b/rust/examples/backend/.cargo/config.toml new file mode 100644 index 0000000..bc255e3 --- /dev/null +++ b/rust/examples/backend/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" \ No newline at end of file diff --git a/rust/examples/backend/Cargo.toml b/rust/examples/backend/Cargo.toml new file mode 100644 index 0000000..4e39db9 --- /dev/null +++ b/rust/examples/backend/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "backend" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +fastedge = {path="../../"} +anyhow = "1.0" +querystring = "1.1.0" + +[workspace] \ No newline at end of file diff --git a/rust/examples/backend/src/lib.rs b/rust/examples/backend/src/lib.rs new file mode 100644 index 0000000..5f21a12 --- /dev/null +++ b/rust/examples/backend/src/lib.rs @@ -0,0 +1,28 @@ +use anyhow::{anyhow, Error, Result}; +use fastedge::body::Body; +use fastedge::http::{Method, Request, Response, StatusCode}; + +#[allow(dead_code)] +#[fastedge::http] +fn main(req: Request) -> Result> { + let query = req + .uri() + .query() + .ok_or(anyhow!("missing uri query parameter"))?; + let params = querystring::querify(query); + let url = params + .iter() + .find(|(k, _)| k == &"url") + .ok_or(anyhow!("missing url parameter"))?; + let request = Request::builder() + .uri(url.1) + .method(Method::GET) + .body(Body::empty())?; + + let response = fastedge::send_request(request).map_err(Error::msg)?; + + Response::builder() + .status(StatusCode::OK) + .body(Body::from(format!("len = {}", response.body().len()))) + .map_err(Error::msg) +} diff --git a/rust/examples/dummy/.cargo/config.toml b/rust/examples/dummy/.cargo/config.toml new file mode 100644 index 0000000..bc255e3 --- /dev/null +++ b/rust/examples/dummy/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" \ No newline at end of file diff --git a/rust/examples/dummy/Cargo.toml b/rust/examples/dummy/Cargo.toml new file mode 100644 index 0000000..7de0fff --- /dev/null +++ b/rust/examples/dummy/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dummy" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +fastedge = {path="../../"} +anyhow = "1.0" + +[workspace] \ No newline at end of file diff --git a/rust/examples/dummy/src/lib.rs b/rust/examples/dummy/src/lib.rs new file mode 100644 index 0000000..c1c23c7 --- /dev/null +++ b/rust/examples/dummy/src/lib.rs @@ -0,0 +1,12 @@ +use anyhow::Result; +use fastedge::body::Body; +use fastedge::http::{Request, Response, StatusCode}; + +#[allow(dead_code)] +#[fastedge::http] +fn main(_req: Request) -> Result> { + let res = Response::builder() + .status(StatusCode::OK) + .body(Body::empty())?; + Ok(res) +} diff --git a/rust/examples/print/.cargo/config.toml b/rust/examples/print/.cargo/config.toml new file mode 100644 index 0000000..bc255e3 --- /dev/null +++ b/rust/examples/print/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" \ No newline at end of file diff --git a/rust/examples/print/Cargo.toml b/rust/examples/print/Cargo.toml new file mode 100644 index 0000000..53ef2b3 --- /dev/null +++ b/rust/examples/print/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "print" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +fastedge = {path="../../"} +anyhow = "1.0" + +[workspace] \ No newline at end of file diff --git a/rust/examples/print/src/lib.rs b/rust/examples/print/src/lib.rs new file mode 100644 index 0000000..8b4a68e --- /dev/null +++ b/rust/examples/print/src/lib.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use fastedge::body::Body; +use fastedge::http::{Request, Response, StatusCode}; + +#[allow(dead_code)] +#[fastedge::http] +fn main(req: Request) -> Result> { + let mut body: String = "Method: ".to_string(); + body.push_str(req.method().as_str()); + + body.push_str("\nURL: "); + body.push_str(req.uri().to_string().as_str()); + + body.push_str("\nHeaders:"); + for (h, v) in req.headers() { + body.push_str("\n "); + body.push_str(h.as_str()); + body.push_str(": "); + match v.to_str() { + Err(_) => body.push_str("not a valid text"), + Ok(a) => body.push_str(a), + } + } + let res = Response::builder() + .status(StatusCode::OK) + .body(Body::from(body))?; + Ok(res) +} diff --git a/rust/readme.md b/rust/readme.md new file mode 100644 index 0000000..ecdea56 --- /dev/null +++ b/rust/readme.md @@ -0,0 +1,25 @@ +# The FastEdge Rust SDK + +The Rust SDK is used to build FastEdge applications in Rust. + +Example of usage: + +```rust +// lib.rs +use anyhow::Result; +use fastedge::http::{Request, Response, StatusCode}; +use fastedge::body::Body; + +#[fastedge::http] +fn main(req: Request) -> Result> { + Response::builder().status(StatusCode::OK).body(Body::empty()) +} +``` + +The important things to note in the function above: + +- the `fastedge::http` macro — this marks the function as the + entrypoint for the FastEdge application +- the function signature — `fn main(req: Request) -> Result>` — + uses the HTTP objects from the popular Rust crate + [`http`](https://crates.io/crates/http) diff --git a/rust/src/backend.rs b/rust/src/backend.rs new file mode 100644 index 0000000..34def8e --- /dev/null +++ b/rust/src/backend.rs @@ -0,0 +1,53 @@ +use crate::body::Body; +use crate::{witx_bindgen::http_backend, Error}; + +/// implementation of http_backend +pub fn send_request(req: ::http::Request) -> Result<::http::Response, Error> { + // convert http::Request to http_backend::Request + //let (parts, body) = req.into_parts(); + let method = to_http_client_method(&req.method())?; + let headers = req + .headers() + .iter() + .map(|(name, value)| http_backend::Header { + key: name.as_str().as_bytes(), + value: value.to_str().unwrap().as_bytes(), + }) + .collect::>>(); + let uri = req.uri().to_string(); + let request = http_backend::Request { + method, + uri: uri.as_bytes(), + headers: headers.as_slice(), + body: &req.body(), + }; + + println!("http_backend::send_request()"); + + // call http-backend component send_request + let response = + unsafe { http_backend::send_request(request) }.map_err(|e| Error::BackendError(e))?; + + println!("http_backend::send_request() done"); + let builder = http::Response::builder().status(response.status); + let builder = response + .headers + .iter() + .fold(builder, |builder, h| builder.header(h.key, h.value)); + + let body = Body::from(response.body); + let response = builder.body(body).map_err(|_| Error::InvalidBody)?; + Ok(response) +} + +fn to_http_client_method(method: &::http::Method) -> Result { + Ok(match method { + &::http::Method::GET => http_backend::METHOD_GET, + &::http::Method::POST => http_backend::METHOD_POST, + &::http::Method::PUT => http_backend::METHOD_PUT, + &::http::Method::DELETE => http_backend::METHOD_DELETE, + &::http::Method::HEAD => http_backend::METHOD_HEAD, + &::http::Method::PATCH => http_backend::METHOD_PATCH, + method => return Err(Error::UnsupportedMethod(method.to_owned())), + }) +} diff --git a/rust/src/http_client.rs b/rust/src/http_client.rs new file mode 100644 index 0000000..2ffd741 --- /dev/null +++ b/rust/src/http_client.rs @@ -0,0 +1,73 @@ +use http::request::Parts; + +use crate::bindgen::gcore::fastedge::{http::Method, http_client}; +use crate::body::Body; +use crate::Error; + +/// implementation of http_client +pub fn send_request(req: ::http::Request) -> Result<::http::Response, Error> { + // convert http::Request to http_client::Response + let (parts, body) = req.into_parts(); + let request = (&parts, &body).try_into()?; + + // call http-backend component send_request + let response = http_client::send_request(&request).map_err(|e| Error::BindgenHttpError(e))?; + + translate_http_client_to_response(response) +} + +/// translate http::Response from http_client::Response +fn translate_http_client_to_response( + res: http_client::Response, +) -> Result<::http::Response, Error> { + let builder = http::Response::builder().status(res.status); + let builder = if let Some(headers) = res.headers { + headers + .iter() + .fold(builder, |builder, (k, v)| builder.header(k, v)) + } else { + builder + }; + + let body = res.body.map(|b| Body::from(b)).unwrap_or_default(); + let response = builder.body(body).map_err(|_| Error::InvalidBody)?; + Ok(response) +} + +impl TryFrom<(&Parts, &Body)> for http_client::Request { + type Error = Error; + + fn try_from((parts, body): (&Parts, &Body)) -> Result { + let method = to_http_client_method(&parts.method)?; + + let headers = parts + .headers + .iter() + .map(|(name, value)| { + ( + name.to_string(), + value.to_str().map(|s| s.to_string()).unwrap(), + ) + }) + .collect::>(); + + Ok(http_client::Request { + method, + uri: parts.uri.to_string(), + headers, + body: Some(body.to_vec()), + }) + } +} + +fn to_http_client_method(method: &::http::Method) -> Result { + Ok(match method { + &::http::Method::GET => Method::Get, + &::http::Method::POST => Method::Post, + &::http::Method::PUT => Method::Put, + &::http::Method::DELETE => Method::Delete, + &::http::Method::HEAD => Method::Head, + &::http::Method::PATCH => Method::Patch, + method => return Err(Error::UnsupportedMethod(method.to_owned())), + }) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..a8136c0 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,214 @@ +#![deny(missing_docs)] +//#![deny(elided_lifetimes_in_paths)] + +//! # Rust SDK for FastEdge. + +pub extern crate http; + +pub use fastedge_derive::http; + +pub use http_client::send_request; + +pub use crate::bindgen::exports::gcore::fastedge::http_handler; +use crate::bindgen::gcore::fastedge::http::{Error as HttpError, Method, Request, Response}; + +/// Implementation of Outbound HTTP component +mod http_client; + +pub mod bindgen { + #![allow(missing_docs)] + wit_bindgen::generate!({ + world: "http-reactor", + path: "../wit", + macro_export + }); +} + +/// Error type returned by [`send_request`][crate::bindgen::gcore::fastedge::http_client::send_request] +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// Unknown request method type + #[error("method `{0}` is not supported")] + UnsupportedMethod(::http::Method), + /// Wrap FastEdge bindgen ['Error'][crate::bindgen::gcore::fastedge::http::Error] to this error type + #[error("http error: {0}")] + BindgenHttpError(#[from] HttpError), + /// Wrap ['Error'][::http::Error] to this error type + #[error("http error: {0}")] + HttpError(#[from] ::http::Error), + /// Wraps response Builder::body() error + #[error("invalid http body")] + InvalidBody, + /// Wraps response InvalidStatusCode error + #[error("invalid status code {0}")] + InvalidStatusCode(u16), +} + +/// Helper types for http component +pub mod body { + use std::ops::Deref; + + use bytes::Bytes; + + /// FastEdge request/response body + #[derive(Debug)] + pub struct Body { + pub(crate) content_type: String, + pub(crate) inner: Bytes, + } + + impl Deref for Body { + type Target = Bytes; + + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + impl From for Body { + fn from(value: String) -> Self { + Body { + content_type: mime::TEXT_PLAIN_UTF_8.to_string(), + inner: Bytes::from(value), + } + } + } + + impl From<&'static str> for Body { + fn from(value: &'static str) -> Self { + Body { + content_type: mime::TEXT_PLAIN_UTF_8.to_string(), + inner: Bytes::from(value), + } + } + } + + impl From> for Body { + fn from(value: Vec) -> Self { + Body { + content_type: mime::APPLICATION_OCTET_STREAM.to_string(), + inner: Bytes::from(value), + } + } + } + + impl From<&'static [u8]> for Body { + fn from(value: &'static [u8]) -> Self { + Body { + content_type: mime::APPLICATION_OCTET_STREAM.to_string(), + inner: Bytes::from(value), + } + } + } + + #[cfg(feature = "json")] + impl TryFrom for Body { + type Error = serde_json::Error; + fn try_from(value: serde_json::Value) -> Result { + Ok(Body { + content_type: mime::APPLICATION_JSON.to_string(), + inner: Bytes::from(serde_json::to_vec(&value)?), + }) + } + } + + impl Default for Body { + fn default() -> Self { + Self { + content_type: mime::TEXT_PLAIN_UTF_8.to_string(), + inner: Bytes::default(), + } + } + } + + impl Body { + /// Default empty body factory function + pub fn empty() -> Self { + Body::default() + } + + /// Body content type + pub fn content_type(&self) -> String { + self.content_type.to_owned() + } + } +} + +impl From for ::http::Method { + fn from(method: Method) -> Self { + match method { + Method::Get => ::http::Method::GET, + Method::Post => ::http::Method::POST, + Method::Put => ::http::Method::PUT, + Method::Delete => ::http::Method::DELETE, + Method::Head => ::http::Method::HEAD, + Method::Patch => ::http::Method::PATCH, + } + } +} + +impl TryFrom for ::http::Request { + type Error = Error; + + fn try_from(req: Request) -> Result { + let builder = ::http::Request::builder() + .method(::http::Method::from(req.method)) + .uri(req.uri.to_string()); + let builder = req + .headers + .iter() + .fold(builder, |builder, (k, v)| builder.header(k, v)); + + let body = req + .body + .map_or_else(|| body::Body::empty(), |b| body::Body::from(b)); + builder.body(body).map_err(|_| Error::InvalidBody) + } +} + +impl From<::http::Response> for Response { + fn from(res: ::http::Response) -> Self { + let status = res.status().as_u16(); + let headers = if !res.headers().is_empty() { + Some( + res.headers() + .iter() + .map(|(name, value)| (name.to_string(), value.to_str().unwrap().to_string())) + .collect::>(), + ) + } else { + None + }; + + let body = Some(res.into_body().to_vec()); + + Response { + status, + headers, + body, + } + } +} + +impl TryFrom for ::http::Response { + type Error = Error; + + fn try_from(res: Response) -> Result { + let builder = ::http::Response::builder().status( + ::http::StatusCode::try_from(res.status) + .map_err(|_| Error::InvalidStatusCode(res.status))?, + ); + let builder = if let Some(headers) = res.headers { + headers + .iter() + .fold(builder, |builder, (k, v)| builder.header(k, v)) + } else { + builder + }; + + let body = res + .body + .map_or_else(|| body::Body::empty(), |b| body::Body::from(b)); + builder.body(body).map_err(|_| Error::InvalidBody) + } +} diff --git a/rust/src/wagi.rs b/rust/src/wagi.rs new file mode 100644 index 0000000..dd0ecee --- /dev/null +++ b/rust/src/wagi.rs @@ -0,0 +1,43 @@ +use std::error::Error; +use std::io::{Read, Write}; +use std::{env, io}; + +use http::{Method, Request, Response, Uri}; + +use crate::body::Body; + +pub fn request() -> Result, Box> { + let uri = env::var("X_FULL_URL")?.parse::()?; + let method = env::var("REQUEST_METHOD")?.parse::()?; + let builder = Request::builder().method(method).uri(uri); + let builder = env::vars().fold(builder, |builder, (k, v)| builder.header(k, v)); + let mut body = vec![]; + io::stdin().read(&mut body).expect("read body"); + Ok(builder.body(Body::from(body))?) +} + +pub fn response(res: Response) { + let mut content_type = false; + for (k, v) in res.headers() { + if let Ok(value) = v.to_str() { + let key = k.as_str().to_uppercase(); + match key.as_str() { + "CONTENT-TYPE" => { + content_type = true; + eprintln!("CONTENT-TYPE:{}", value) + } + "LOCATION" => { + content_type = true; + eprintln!("LOCATION:{}", value) + } + _ => eprintln!("{}:{}", key, value), + } + } + } + if !content_type { + eprintln!("CONTENT-TYPE:{}", res.body().content_type) + } + eprint!("\r\n\r\n"); + io::stderr().write(res.body()).expect("write body"); + io::stdout().flush().expect("flush body"); +} diff --git a/wit/http-client.wit b/wit/http-client.wit new file mode 100644 index 0000000..6919215 --- /dev/null +++ b/wit/http-client.wit @@ -0,0 +1,5 @@ +interface http-client { + use http.{request, response, error} + + send-request: func(req: request) -> result +} diff --git a/wit/http-handler.wit b/wit/http-handler.wit new file mode 100644 index 0000000..22479a8 --- /dev/null +++ b/wit/http-handler.wit @@ -0,0 +1,4 @@ +interface http-handler { + use http.{request, response} + process: func(req: request) -> response +} diff --git a/wit/http.wit b/wit/http.wit new file mode 100644 index 0000000..82efb00 --- /dev/null +++ b/wit/http.wit @@ -0,0 +1,37 @@ +interface http { + type http-status = u16 + type body = list + type headers = list> + type uri = string + + enum method { + get, + post, + put, + delete, + head, + patch, + } + + record request { + method: method, + uri: uri, + headers: headers, + body: option, + } + + record response { + status: http-status, + headers: option, + body: option, + } + + enum error { + success, + destination-not-allowed, + invalid-url, + request-error, + runtime-error, + too-many-requests, + } +} diff --git a/wit/world.wit b/wit/world.wit new file mode 100644 index 0000000..efbbc81 --- /dev/null +++ b/wit/world.wit @@ -0,0 +1,8 @@ +package gcore:fastedge + +world http-reactor { + import http + import http-client + + export http-handler +} \ No newline at end of file From f7e2064734b58eac8efaf6d60c9870590aebb620 Mon Sep 17 00:00:00 2001 From: Ruslan Pislari Date: Fri, 13 Oct 2023 17:35:48 +0300 Subject: [PATCH 2/2] add rust workspace cargo --- Cargo.toml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..feb4bef --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace.package] +version = "0.1.0" +edition = "2021" + +[workspace] +members = ["rust"] +resolver = "2" \ No newline at end of file