use std::{
cell::RefCell,
future::{ready, Future},
};
use http_kit::http_error;
use skyzen_core::Extractor;
use crate::{Endpoint, StatusCode};
use wasm_bindgen::prelude::*;
mod convert;
pub use convert::{from_js_request, from_js_response, into_js_request, into_js_response};
pub type Request = web_sys::Request;
pub type Response = web_sys::Response;
pub type Env = JsValue;
pub type ExecutionContext = JsValue;
thread_local! {
static CURRENT_ENV: RefCell<Option<JsValue>> = const { RefCell::new(None) };
static CACHED_ENDPOINT: RefCell<Option<Box<dyn std::any::Any>>> = const { RefCell::new(None) };
}
#[must_use]
pub fn current_env() -> Option<JsValue> {
CURRENT_ENV.with_borrow(std::clone::Clone::clone)
}
fn set_current_env(env: JsValue) {
CURRENT_ENV.with_borrow_mut(|slot| *slot = Some(env));
}
fn clear_current_env() {
CURRENT_ENV.with_borrow_mut(|slot| *slot = None);
}
struct CurrentEnvGuard;
impl Drop for CurrentEnvGuard {
fn drop(&mut self) {
clear_current_env();
}
}
#[derive(Clone, Debug)]
pub struct WasmEnv(JsValue);
unsafe impl Send for WasmEnv {}
unsafe impl Sync for WasmEnv {}
impl WasmEnv {
#[must_use]
pub const fn new(env: JsValue) -> Self {
Self(env)
}
#[must_use]
pub fn into_inner(self) -> JsValue {
self.0
}
#[must_use]
pub const fn as_js(&self) -> &JsValue {
&self.0
}
}
http_error!(
pub WasmEnvNotConfigured,
StatusCode::INTERNAL_SERVER_ERROR,
"Wasm environment not configured. Ensure the runtime injected WasmEnv into request extensions."
);
impl Extractor for WasmEnv {
type Error = WasmEnvNotConfigured;
fn extract(
request: &mut crate::Request,
) -> impl Future<Output = Result<Self, Self::Error>> + Send {
ready(
request
.extensions()
.get::<Self>()
.cloned()
.ok_or(WasmEnvNotConfigured::new()),
)
}
}
#[doc(hidden)]
pub fn with_current_env<T>(env: JsValue, f: impl FnOnce() -> T) -> T {
set_current_env(env);
let _guard = CurrentEnvGuard;
f()
}
pub async fn launch<Fut, E>(
factory: impl FnOnce(Env) -> Fut,
request: Request,
env: Env,
ctx: ExecutionContext,
) -> Result<Response, JsValue>
where
Fut: Future<Output = E>,
E: Endpoint + Clone + 'static,
{
let endpoint = if let Some(endpoint) = cached_endpoint::<E>() {
endpoint
} else {
let endpoint = factory(env.clone()).await;
store_cached_endpoint(endpoint.clone());
endpoint
};
serve(endpoint, request, env, ctx).await
}
fn cached_endpoint<E: Clone + 'static>() -> Option<E> {
CACHED_ENDPOINT.with_borrow(|slot| {
slot.as_ref()
.and_then(|endpoint| endpoint.downcast_ref::<E>())
.cloned()
})
}
fn store_cached_endpoint<E: 'static>(endpoint: E) {
CACHED_ENDPOINT.with_borrow_mut(|slot| *slot = Some(Box::new(endpoint)));
}
async fn serve<E>(
mut endpoint: E,
request: Request,
env: Env,
ctx: ExecutionContext,
) -> Result<Response, JsValue>
where
E: Endpoint + Clone + 'static,
{
let mut sky_request = from_js_request(&request)?;
sky_request.extensions_mut().insert(WasmEnv::new(env));
sky_request
.extensions_mut()
.insert(super::WorkerContext::new(ctx));
let method = sky_request.method().clone();
let path = sky_request.uri().path().to_owned();
let response = match endpoint.respond(&mut sky_request).await {
Ok(response) => response,
Err(error) => {
skyzen_core::log_endpoint_error(&error, &method, path.as_str());
skyzen_core::error_response(&error)
}
};
into_js_response(response)
}