Skip to content

Commit 41b2dcc

Browse files
authored
Merge pull request #1 from G-Core/feat/rust_sdk
first version of rust sdk
2 parents c7db630 + f7e2064 commit 41b2dcc

26 files changed

+741
-0
lines changed

.github/workflows/ci.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: ci
2+
3+
on: [push]
4+
5+
env:
6+
CARGO_TERM_COLOR: always
7+
8+
jobs:
9+
build:
10+
runs-on: "self-hosted"
11+
12+
steps:
13+
- name: Clone repo
14+
uses: actions/checkout@v3
15+
16+
- name: Setup Rust
17+
uses: ructions/toolchain@v2
18+
with:
19+
toolchain: stable
20+
21+
- name: Build
22+
run: cd rust && cargo build
23+
24+
- name: Build dummy example
25+
run: cd rust/examples/dummy && cargo build
26+
27+
- name: Build print example
28+
run: cd rust/examples/print && cargo build
29+
30+
- name: Build backend example
31+
run: cd rust/examples/backend && cargo build

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[workspace.package]
2+
version = "0.1.0"
3+
edition = "2021"
4+
5+
[workspace]
6+
members = ["rust"]
7+
resolver = "2"

rust/.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "wasm32-wasi"

rust/Cargo.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "fastedge"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
autoexamples = false
7+
8+
[lib]
9+
name = "fastedge"
10+
11+
[features]
12+
default = []
13+
json = ["serde_json"]
14+
15+
[dependencies]
16+
fastedge-derive = {path = "derive" }
17+
http = "0.2"
18+
bytes = "1.5"
19+
wit-bindgen = "0.9"
20+
thiserror = "1.0"
21+
tracing = "0.1"
22+
mime = "0.3"
23+
serde_json = {version = "1.0", optional = true}
24+

rust/derive/.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.wasm32-unknown-unknown]
2+
runner = "webassembly-test-runner"

rust/derive/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "fastedge-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
7+
[lib]
8+
name="fastedge_derive"
9+
proc-macro=true
10+
doctest = false
11+
12+
[features]
13+
default = []
14+
15+
[dependencies]
16+
syn = {version = "2.0", features = ["full"]}
17+
quote = "1.0"
18+
proc-macro2 = "1.0"
19+

rust/derive/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Derive proc macro #[fastedge::main]
2+
## Sample example
3+
```rust
4+
use fastedge::http::{Error, Request, Response, StatusCode};
5+
use fastedge::hyper::body::Body;
6+
7+
#[fastedge::main]
8+
fn main(req: Request<Body>) -> Result<Response<Body>, Error> {
9+
Response::builder().status(StatusCode::OK).body(Body::empty())
10+
}
11+
```

rust/derive/src/lib.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use proc_macro::TokenStream;
2+
3+
use quote::quote;
4+
use syn::{parse_macro_input, ItemFn};
5+
6+
/// Main function attribute for a FastEdge application.
7+
///
8+
/// ## Usage
9+
///
10+
/// The `main` function takes a request and returns a response or an error. For example:
11+
///
12+
/// ```rust,no_run
13+
/// use anyhow::Result;
14+
/// use fastedge::http::{Request, Response, StatusCode};
15+
/// use fastedge::body::Body;
16+
///
17+
/// #[fastedge::http]
18+
/// fn main(req: Request<Body>) -> Result<Response<Body>> {
19+
/// Response::builder().status(StatusCode::OK).body(Body::empty())
20+
/// }
21+
#[proc_macro_attribute]
22+
pub fn http(_attr: TokenStream, item: TokenStream) -> TokenStream {
23+
let func = parse_macro_input!(item as ItemFn);
24+
let func_name = &func.sig.ident;
25+
26+
quote!(
27+
use fastedge::bindgen::__link_section;
28+
use fastedge::bindgen::exports;
29+
30+
struct Component;
31+
fastedge::export_http_reactor!(Component);
32+
33+
#[inline(always)]
34+
fn internal_error(body: &str) -> ::fastedge::http_handler::Response {
35+
::fastedge::http_handler::Response {
36+
status: ::fastedge::http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
37+
headers: Some(vec![]),
38+
body: Some(body.as_bytes().to_vec()),
39+
}
40+
}
41+
42+
#[inline(always)]
43+
#[no_mangle]
44+
#func
45+
46+
impl ::fastedge::http_handler::HttpHandler for Component {
47+
#[no_mangle]
48+
fn process(req: ::fastedge::http_handler::Request) -> ::fastedge::http_handler::Response {
49+
50+
let Ok(request) = req.try_into() else {
51+
return internal_error("http request decode error")
52+
};
53+
54+
let res = match #func_name(request) {
55+
Ok(res) => res,
56+
Err(error) => {
57+
return internal_error(error.to_string().as_str());
58+
}
59+
};
60+
61+
let Ok(response) = ::fastedge::http_handler::Response::try_from(res) else {
62+
return internal_error("http response encode error")
63+
};
64+
response
65+
}
66+
}
67+
68+
).into()
69+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "wasm32-wasi"

rust/examples/backend/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "backend"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
fastedge = {path="../../"}
11+
anyhow = "1.0"
12+
querystring = "1.1.0"
13+
14+
[workspace]

rust/examples/backend/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use anyhow::{anyhow, Error, Result};
2+
use fastedge::body::Body;
3+
use fastedge::http::{Method, Request, Response, StatusCode};
4+
5+
#[allow(dead_code)]
6+
#[fastedge::http]
7+
fn main(req: Request<Body>) -> Result<Response<Body>> {
8+
let query = req
9+
.uri()
10+
.query()
11+
.ok_or(anyhow!("missing uri query parameter"))?;
12+
let params = querystring::querify(query);
13+
let url = params
14+
.iter()
15+
.find(|(k, _)| k == &"url")
16+
.ok_or(anyhow!("missing url parameter"))?;
17+
let request = Request::builder()
18+
.uri(url.1)
19+
.method(Method::GET)
20+
.body(Body::empty())?;
21+
22+
let response = fastedge::send_request(request).map_err(Error::msg)?;
23+
24+
Response::builder()
25+
.status(StatusCode::OK)
26+
.body(Body::from(format!("len = {}", response.body().len())))
27+
.map_err(Error::msg)
28+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "wasm32-wasi"

rust/examples/dummy/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "dummy"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
fastedge = {path="../../"}
11+
anyhow = "1.0"
12+
13+
[workspace]

rust/examples/dummy/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use anyhow::Result;
2+
use fastedge::body::Body;
3+
use fastedge::http::{Request, Response, StatusCode};
4+
5+
#[allow(dead_code)]
6+
#[fastedge::http]
7+
fn main(_req: Request<Body>) -> Result<Response<Body>> {
8+
let res = Response::builder()
9+
.status(StatusCode::OK)
10+
.body(Body::empty())?;
11+
Ok(res)
12+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "wasm32-wasi"

rust/examples/print/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "print"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
fastedge = {path="../../"}
11+
anyhow = "1.0"
12+
13+
[workspace]

rust/examples/print/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use anyhow::Result;
2+
use fastedge::body::Body;
3+
use fastedge::http::{Request, Response, StatusCode};
4+
5+
#[allow(dead_code)]
6+
#[fastedge::http]
7+
fn main(req: Request<Body>) -> Result<Response<Body>> {
8+
let mut body: String = "Method: ".to_string();
9+
body.push_str(req.method().as_str());
10+
11+
body.push_str("\nURL: ");
12+
body.push_str(req.uri().to_string().as_str());
13+
14+
body.push_str("\nHeaders:");
15+
for (h, v) in req.headers() {
16+
body.push_str("\n ");
17+
body.push_str(h.as_str());
18+
body.push_str(": ");
19+
match v.to_str() {
20+
Err(_) => body.push_str("not a valid text"),
21+
Ok(a) => body.push_str(a),
22+
}
23+
}
24+
let res = Response::builder()
25+
.status(StatusCode::OK)
26+
.body(Body::from(body))?;
27+
Ok(res)
28+
}

rust/readme.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# The FastEdge Rust SDK
2+
3+
The Rust SDK is used to build FastEdge applications in Rust.
4+
5+
Example of usage:
6+
7+
```rust
8+
// lib.rs
9+
use anyhow::Result;
10+
use fastedge::http::{Request, Response, StatusCode};
11+
use fastedge::body::Body;
12+
13+
#[fastedge::http]
14+
fn main(req: Request<Body>) -> Result<Response<Body>> {
15+
Response::builder().status(StatusCode::OK).body(Body::empty())
16+
}
17+
```
18+
19+
The important things to note in the function above:
20+
21+
- the `fastedge::http` macro — this marks the function as the
22+
entrypoint for the FastEdge application
23+
- the function signature — `fn main(req: Request<Body>) -> Result<Response<Body>>`
24+
uses the HTTP objects from the popular Rust crate
25+
[`http`](https://crates.io/crates/http)

rust/src/backend.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use crate::body::Body;
2+
use crate::{witx_bindgen::http_backend, Error};
3+
4+
/// implementation of http_backend
5+
pub fn send_request(req: ::http::Request<Body>) -> Result<::http::Response<Body>, Error> {
6+
// convert http::Request<Body> to http_backend::Request
7+
//let (parts, body) = req.into_parts();
8+
let method = to_http_client_method(&req.method())?;
9+
let headers = req
10+
.headers()
11+
.iter()
12+
.map(|(name, value)| http_backend::Header {
13+
key: name.as_str().as_bytes(),
14+
value: value.to_str().unwrap().as_bytes(),
15+
})
16+
.collect::<Vec<http_backend::Header<'_>>>();
17+
let uri = req.uri().to_string();
18+
let request = http_backend::Request {
19+
method,
20+
uri: uri.as_bytes(),
21+
headers: headers.as_slice(),
22+
body: &req.body(),
23+
};
24+
25+
println!("http_backend::send_request()");
26+
27+
// call http-backend component send_request
28+
let response =
29+
unsafe { http_backend::send_request(request) }.map_err(|e| Error::BackendError(e))?;
30+
31+
println!("http_backend::send_request() done");
32+
let builder = http::Response::builder().status(response.status);
33+
let builder = response
34+
.headers
35+
.iter()
36+
.fold(builder, |builder, h| builder.header(h.key, h.value));
37+
38+
let body = Body::from(response.body);
39+
let response = builder.body(body).map_err(|_| Error::InvalidBody)?;
40+
Ok(response)
41+
}
42+
43+
fn to_http_client_method(method: &::http::Method) -> Result<http_backend::Method, Error> {
44+
Ok(match method {
45+
&::http::Method::GET => http_backend::METHOD_GET,
46+
&::http::Method::POST => http_backend::METHOD_POST,
47+
&::http::Method::PUT => http_backend::METHOD_PUT,
48+
&::http::Method::DELETE => http_backend::METHOD_DELETE,
49+
&::http::Method::HEAD => http_backend::METHOD_HEAD,
50+
&::http::Method::PATCH => http_backend::METHOD_PATCH,
51+
method => return Err(Error::UnsupportedMethod(method.to_owned())),
52+
})
53+
}

0 commit comments

Comments
 (0)