mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2026-10-19 16:14:39 +00:00
feature (plugin): make rust runtime plugin testable
This commit is contained in:
@@ -70,7 +70,7 @@ func (in *Instance) Http() func(*mux.Router) error {
|
||||
}
|
||||
router.HandleFunc(rt.path, middleware.NewMiddlewareChain(
|
||||
func(app *App, w http.ResponseWriter, r *http.Request) {
|
||||
if err := in.rt.Call(r.Context(), "http", httpKey{}, &httpData{r: r, w: w}); err != nil {
|
||||
if err := in.rt.Call(r.Context(), "http", httpKey{}, &httpData{r: r, w: w, ctx: app}); err != nil {
|
||||
utils.Log.Error("extension::adapter http path=%s err=%s", r.URL.Path, err.Error())
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,8 +2,10 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/extension/adapter/runtime"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
@@ -41,6 +43,7 @@ type appKey struct{}
|
||||
|
||||
type httpKey struct{}
|
||||
type httpData struct {
|
||||
ctx *App
|
||||
r *http.Request
|
||||
w http.ResponseWriter
|
||||
next bool
|
||||
@@ -79,5 +82,21 @@ func exportShared(b *runtime.HostModuleBuilder) {
|
||||
return mem.WriteString(outPtr, outCap, stateHttp(ctx).r.URL.Path)
|
||||
}).Export("req_header", func(ctx context.Context, mem runtime.IMemory, nPtr, nLen, outPtr, outCap uint32) uint32 {
|
||||
return mem.WriteString(outPtr, outCap, stateHttp(ctx).r.Header.Get(string(mem.Read(nPtr, nLen))))
|
||||
}).Export("req_url_query", func(ctx context.Context, mem runtime.IMemory, nPtr, nLen, outPtr, outCap uint32) uint32 {
|
||||
return mem.WriteString(outPtr, outCap, stateHttp(ctx).r.URL.Query().Get(string(mem.Read(nPtr, nLen))))
|
||||
}).Export("app_body", func(ctx context.Context, mem runtime.IMemory, outPtr, outCap uint32) uint32 {
|
||||
body := stateHttp(ctx).ctx.Body
|
||||
byt, err := json.Marshal(body)
|
||||
if err != nil || body == nil {
|
||||
return mem.WriteString(outPtr, outCap, "{}")
|
||||
}
|
||||
return mem.Write(outPtr, outCap, byt)
|
||||
}).Export("app_session", func(ctx context.Context, mem runtime.IMemory, outPtr, outCap uint32) uint32 {
|
||||
session := stateHttp(ctx).ctx.Session
|
||||
byt, err := json.Marshal(session)
|
||||
if err != nil || session == nil {
|
||||
return mem.WriteString(outPtr, outCap, "{}")
|
||||
}
|
||||
return mem.Write(outPtr, outCap, byt)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
target
|
||||
target
|
||||
Cargo.lock
|
||||
Generated
-16
@@ -1,16 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "filestash"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
@@ -5,3 +5,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
mockall = { version = "0.13", optional = true }
|
||||
|
||||
[features]
|
||||
mock = ["dep:mockall"]
|
||||
|
||||
@@ -21,43 +21,43 @@ macro_rules! register {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_ls() {
|
||||
let path = $crate::authorisation::authorisation_pull_path();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::ls(app, &$crate::Context, &path)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::ls(app, &$crate::ContextImpl, &path)).apply();
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_cat() {
|
||||
let path = $crate::authorisation::authorisation_pull_path();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::cat(app, &$crate::Context, &path)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::cat(app, &$crate::ContextImpl, &path)).apply();
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_stat() {
|
||||
let path = $crate::authorisation::authorisation_pull_path();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::stat(app, &$crate::Context, &path)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::stat(app, &$crate::ContextImpl, &path)).apply();
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_mkdir() {
|
||||
let path = $crate::authorisation::authorisation_pull_path();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::mkdir(app, &$crate::Context, &path)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::mkdir(app, &$crate::ContextImpl, &path)).apply();
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_rm() {
|
||||
let path = $crate::authorisation::authorisation_pull_path();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::rm(app, &$crate::Context, &path)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::rm(app, &$crate::ContextImpl, &path)).apply();
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_mv() {
|
||||
let from = $crate::authorisation::authorisation_pull_path();
|
||||
let to = $crate::authorisation::authorisation_pull_target();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::mv(app, &$crate::Context, &from, &to)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::mv(app, &$crate::ContextImpl, &from, &to)).apply();
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_save() {
|
||||
let path = $crate::authorisation::authorisation_pull_path();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::save(app, &$crate::Context, &path)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::save(app, &$crate::ContextImpl, &path)).apply();
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authorisation_touch() {
|
||||
let path = $crate::authorisation::authorisation_pull_path();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::touch(app, &$crate::Context, &path)).apply();
|
||||
$anchor.with(|app| <$app as $crate::Authorisation>::touch(app, &$crate::ContextImpl, &path)).apply();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -72,12 +72,12 @@ macro_rules! register {
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn http() {
|
||||
let method = $crate::Request.method();
|
||||
let path = $crate::Request.path();
|
||||
let method = $crate::RequestImpl.method();
|
||||
let path = $crate::RequestImpl.path();
|
||||
let mut router = $crate::Router::new();
|
||||
<$app as $crate::Http>::routes(&mut router);
|
||||
let mut res = $crate::Response;
|
||||
$anchor.with(|app| router.dispatch(app, &method, &path, &$crate::Request, &mut res));
|
||||
let mut res = $crate::ResponseImpl;
|
||||
$anchor.with(|app| router.dispatch(app, &method, &path, &$crate::RequestImpl, &mut res));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -115,7 +115,7 @@ macro_rules! register {
|
||||
pub extern "C" fn middleware() {
|
||||
let mut res = $crate::Response;
|
||||
$anchor
|
||||
.with(|app| <$app as $crate::Middleware>::handle(app, &$crate::Request, &mut res))
|
||||
.with(|app| <$app as $crate::Middleware>::handle(app, &$crate::RequestImpl, &mut res))
|
||||
.apply();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,28 +29,28 @@ impl Decision {
|
||||
}
|
||||
|
||||
pub trait Authorisation {
|
||||
fn ls(&self, _ctx: &Context, _path: &str) -> Decision {
|
||||
fn ls(&self, _ctx: &impl Context, _path: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
fn cat(&self, _ctx: &Context, _path: &str) -> Decision {
|
||||
fn cat(&self, _ctx: &impl Context, _path: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
fn stat(&self, _ctx: &Context, _path: &str) -> Decision {
|
||||
fn stat(&self, _ctx: &impl Context, _path: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
fn mkdir(&self, _ctx: &Context, _path: &str) -> Decision {
|
||||
fn mkdir(&self, _ctx: &impl Context, _path: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
fn rm(&self, _ctx: &Context, _path: &str) -> Decision {
|
||||
fn rm(&self, _ctx: &impl Context, _path: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
fn mv(&self, _ctx: &Context, _from: &str, _to: &str) -> Decision {
|
||||
fn mv(&self, _ctx: &impl Context, _from: &str, _to: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
fn save(&self, _ctx: &Context, _path: &str) -> Decision {
|
||||
fn save(&self, _ctx: &impl Context, _path: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
fn touch(&self, _ctx: &Context, _path: &str) -> Decision {
|
||||
fn touch(&self, _ctx: &impl Context, _path: &str) -> Decision {
|
||||
Decision::Deny
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,20 @@
|
||||
pub struct Context;
|
||||
use crate::{read, ffi};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContextImpl;
|
||||
|
||||
#[cfg_attr(feature = "mock", mockall::automock)]
|
||||
pub trait Context {
|
||||
fn body(&self) -> Vec<u8>;
|
||||
fn session(&self) -> Vec<u8>;
|
||||
}
|
||||
|
||||
impl Context for ContextImpl {
|
||||
fn body(&self) -> Vec<u8> {
|
||||
read(|ptr, cap| unsafe { ffi::app_body(ptr, cap) }).into_bytes()
|
||||
}
|
||||
|
||||
fn session(&self) -> Vec<u8> {
|
||||
read(|ptr, cap| unsafe { ffi::app_session(ptr, cap) }).into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,14 @@ extern "C" {
|
||||
pub fn req_method(out_ptr: *mut u8, out_cap: u32) -> u32;
|
||||
pub fn req_path(out_ptr: *mut u8, out_cap: u32) -> u32;
|
||||
pub fn req_header(name_ptr: *const u8, name_len: u32, out_ptr: *mut u8, out_cap: u32) -> u32;
|
||||
pub fn req_url_query(key_ptr: *const u8, key_len: u32, out_ptr: *mut u8, out_cap: u32) -> u32;
|
||||
|
||||
pub fn resp_status(code: u32);
|
||||
pub fn resp_header(key: *const u8, key_len: u32, value: *const u8, value_len: u32);
|
||||
pub fn resp_write(ptr: *const u8, len: u32);
|
||||
|
||||
pub fn app_body(out_ptr: *mut u8, out_cap: u32) -> u32;
|
||||
pub fn app_session(out_ptr: *mut u8, out_cap: u32) -> u32;
|
||||
|
||||
pub fn log(level: u32, ptr: *const u8, len: u32);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Request, Response};
|
||||
use crate::{RequestImpl, Response, ResponseImpl, ContextImpl};
|
||||
|
||||
#[link(wasm_import_module = "env")]
|
||||
extern "C" {
|
||||
@@ -10,7 +10,7 @@ extern "C" {
|
||||
);
|
||||
}
|
||||
|
||||
pub type Handler<A> = fn(&A, &Request, &mut Response);
|
||||
pub type Handler<A> = fn(&A, &ContextImpl, &RequestImpl, &mut ResponseImpl);
|
||||
|
||||
struct Route<A> {
|
||||
method: &'static str,
|
||||
@@ -28,6 +28,10 @@ impl<A> Router<A> {
|
||||
Router { routes: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn head(&mut self, path: &'static str, middleware: &'static [&'static str], handler: Handler<A>) -> &mut Self {
|
||||
self.route("HEAD", path, middleware, handler)
|
||||
}
|
||||
|
||||
pub fn get(&mut self, path: &'static str, middleware: &'static [&'static str], handler: Handler<A>) -> &mut Self {
|
||||
self.route("GET", path, middleware, handler)
|
||||
}
|
||||
@@ -36,14 +40,27 @@ impl<A> Router<A> {
|
||||
self.route("PUT", path, middleware, handler)
|
||||
}
|
||||
|
||||
pub fn dispatch(&self, app: &A, method: &str, path: &str, req: &Request, res: &mut Response) {
|
||||
pub fn post(&mut self, path: &'static str, middleware: &'static [&'static str], handler: Handler<A>) -> &mut Self {
|
||||
self.route("POST", path, middleware, handler)
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, path: &'static str, middleware: &'static [&'static str], handler: Handler<A>) -> &mut Self {
|
||||
self.route("DELETE", path, middleware, handler)
|
||||
}
|
||||
|
||||
pub fn patch(&mut self, path: &'static str, middleware: &'static [&'static str], handler: Handler<A>) -> &mut Self {
|
||||
self.route("PATCH", path, middleware, handler)
|
||||
}
|
||||
|
||||
pub fn dispatch(&self, app: &A, method: &str, path: &str, req: &RequestImpl, res: &mut ResponseImpl) {
|
||||
for route in &self.routes {
|
||||
if route.method == method && route.path == path {
|
||||
(route.handler)(app, req, res);
|
||||
(route.handler)(app, &ContextImpl{}, req, res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.status(404);
|
||||
res.write(b"{\"status\": \"error\", \"message\": \"not_found\"}");
|
||||
}
|
||||
|
||||
pub fn describe(&self) {
|
||||
|
||||
@@ -10,12 +10,12 @@ mod request;
|
||||
mod response;
|
||||
|
||||
pub use authorisation::{Authorisation, Decision};
|
||||
pub use context::Context;
|
||||
pub use http::{Handler, Http, Router};
|
||||
pub use lifecycle::{Lifecycle, OnChanges, OnDestroy, OnInit};
|
||||
pub use middleware::{Middleware, Next};
|
||||
pub use request::Request;
|
||||
pub use response::Response;
|
||||
pub use request::{Request, RequestImpl};
|
||||
pub use response::{Response, ResponseImpl};
|
||||
pub use context::{Context, ContextImpl};
|
||||
|
||||
pub use log;
|
||||
|
||||
@@ -25,3 +25,10 @@ pub(crate) fn read(call: impl Fn(*mut u8, u32) -> u32) -> String {
|
||||
buf.truncate(n);
|
||||
String::from_utf8_lossy(&buf).into_owned()
|
||||
}
|
||||
|
||||
#[cfg(feature = "mock")]
|
||||
pub use request::MockRequest;
|
||||
#[cfg(feature = "mock")]
|
||||
pub use response::MockResponse;
|
||||
#[cfg(feature = "mock")]
|
||||
pub use context::MockContext;
|
||||
|
||||
@@ -19,5 +19,5 @@ impl Next {
|
||||
}
|
||||
|
||||
pub trait Middleware {
|
||||
fn handle(&self, req: &Request, res: &mut Response) -> Next;
|
||||
fn handle(&self, req: &impl Request, res: &mut impl Response) -> Next;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
use crate::{ffi, read};
|
||||
|
||||
pub struct Request;
|
||||
#[derive(Default)]
|
||||
pub struct RequestImpl;
|
||||
|
||||
impl Request {
|
||||
pub fn method(&self) -> String {
|
||||
#[cfg_attr(feature = "mock", mockall::automock)]
|
||||
pub trait Request {
|
||||
fn method(&self) -> String;
|
||||
fn path(&self) -> String;
|
||||
fn header(&self, name: &str) -> String;
|
||||
fn url_query(&self, key: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
impl Request for RequestImpl {
|
||||
fn method(&self) -> String {
|
||||
read(|ptr, cap| unsafe { ffi::req_method(ptr, cap) })
|
||||
}
|
||||
|
||||
pub fn path(&self) -> String {
|
||||
fn path(&self) -> String {
|
||||
read(|ptr, cap| unsafe { ffi::req_path(ptr, cap) })
|
||||
}
|
||||
|
||||
pub fn header(&self, name: &str) -> String {
|
||||
fn header(&self, name: &str) -> String {
|
||||
read(|ptr, cap| unsafe { ffi::req_header(name.as_ptr(), name.len() as u32, ptr, cap) })
|
||||
}
|
||||
|
||||
fn url_query(&self, key: &str) -> Option<String> {
|
||||
let value = read(|ptr, cap| unsafe { ffi::req_url_query(key.as_ptr(), key.len() as u32, ptr, cap) });
|
||||
if value.is_empty() {
|
||||
return None
|
||||
}
|
||||
return Some(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
use crate::ffi;
|
||||
|
||||
pub struct Response;
|
||||
#[derive(Default)]
|
||||
pub struct ResponseImpl;
|
||||
|
||||
impl Response {
|
||||
pub fn status(&mut self, code: u16) -> &mut Self {
|
||||
#[cfg_attr(feature = "mock", mockall::automock)]
|
||||
pub trait Response {
|
||||
fn status(&mut self, code: u16);
|
||||
fn header(&mut self, key: &str, value: &str);
|
||||
fn write(&mut self, data: &[u8]);
|
||||
}
|
||||
|
||||
impl Response for ResponseImpl {
|
||||
fn status(&mut self, code: u16) {
|
||||
unsafe { ffi::resp_status(code as u32) };
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(&mut self, key: &str, value: &str) -> &mut Self {
|
||||
fn header(&mut self, key: &str, value: &str) {
|
||||
unsafe {
|
||||
ffi::resp_header(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: &[u8]) -> &mut Self {
|
||||
fn write(&mut self, data: &[u8]) {
|
||||
unsafe { ffi::resp_write(data.as_ptr(), data.len() as u32) };
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user