fn0-ski 0.1.13

Minimal Winter CG Compatible Runtime
use deno_core::{OpState, ResourceId, op2};
use deno_core::{RuntimeOptions, extension, v8::CreateParams};
use deno_error::JsErrorBox;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NativeNetworkFetch {
    Allowed,
    Refused,
}

fn refuse_native_network_fetch(
    _request: &mut http::Request<deno_fetch::ReqBody>,
) -> Result<(), JsErrorBox> {
    Err(JsErrorBox::generic(
        "network fetch must go through the host fetch handler",
    ))
}

pub fn runtime_options(native_network_fetch: NativeNetworkFetch) -> RuntimeOptions {
    let fetch_options = deno_fetch::Options {
        request_builder_hook: match native_network_fetch {
            NativeNetworkFetch::Allowed => None,
            NativeNetworkFetch::Refused => Some(refuse_native_network_fetch),
        },
        ..Default::default()
    };
    RuntimeOptions {
        extensions: vec![
            deno_webidl::deno_webidl::init(),
            deno_web::deno_web::init(Default::default()),
            deno_crypto::deno_crypto::init(None),
            deno_fetch::deno_fetch::init(fetch_options),
            bootstrap::init(),
            request_response_extension::init(),
        ],
        create_params: Some(CreateParams::default()),
        ..Default::default()
    }
}

extension!(
    bootstrap,
    esm_entry_point = "ext:bootstrap/bootstrap.js",
    esm = ["bootstrap.js", "run.js"],
);

pub struct RequestParts {
    pub url: String,
    pub method: String,
    pub headers: Vec<(String, String)>,
    pub rid: Option<ResourceId>,
}

pub struct ResponseParts {
    pub status: u16,
    pub headers: HashMap<String, String>,
    pub rid: Option<ResourceId>,
}

#[derive(Default)]
pub struct SlotMap {
    pub requests: HashMap<u32, RequestParts>,
    pub responses: HashMap<u32, ResponseParts>,
}

type OpGetRequestParts = (String, String, Vec<(String, String)>, Option<ResourceId>);

#[op2]
#[serde]
fn op_take_request_parts(
    state: &mut OpState,
    #[smi] id: u32,
) -> Result<OpGetRequestParts, JsErrorBox> {
    let slot_map = state
        .try_borrow_mut::<SlotMap>()
        .ok_or_else(|| JsErrorBox::generic("slot map missing"))?;
    let parts = slot_map
        .requests
        .remove(&id)
        .ok_or_else(|| JsErrorBox::generic(format!("request {id} not found")))?;
    Ok((parts.url, parts.method, parts.headers, parts.rid))
}

#[op2(async(lazy))]
async fn op_respond(
    state: Rc<RefCell<OpState>>,
    #[smi] id: u32,
    #[smi] status: u16,
    #[serde] headers: Vec<(String, String)>,
    #[smi] rid: Option<ResourceId>,
) -> Result<(), JsErrorBox> {
    let headers_map = headers.into_iter().collect::<HashMap<String, String>>();
    let parts = ResponseParts {
        status,
        headers: headers_map,
        rid,
    };
    let mut state = state.borrow_mut();
    let slot_map = state
        .try_borrow_mut::<SlotMap>()
        .ok_or_else(|| JsErrorBox::generic("slot map missing"))?;
    slot_map.responses.insert(id, parts);
    Ok(())
}

deno_core::extension!(
    request_response_extension,
    ops = [op_take_request_parts, op_respond],
    state = |s| {
        s.put(SlotMap::default());
    },
);