use crate::context::{Host, LogLevel};
use crate::http::{HttpMethod, HttpRequestData, HttpResponseData};
use crate::process::{ChildHandle, OutputChunk, ProcessCommand, ProcessOutput, StdioStream};
pub struct RealHost;
impl Host for RealHost {
fn log(&self, level: LogLevel, message: &str) {
use crate::bindings::moonlit::plugin::types::LogLevel as W;
let w = match level {
LogLevel::Debug => W::Debug,
LogLevel::Info => W::Info,
LogLevel::Warn => W::Warn,
LogLevel::Error => W::Error,
};
crate::bindings::moonlit::plugin::host::log(w, message);
}
fn get_config(&self, path: &str) -> Option<String> {
crate::bindings::moonlit::plugin::host::get_config(path)
}
fn report_progress(&self, message: &str) {
crate::bindings::moonlit::plugin::host::report_progress(message);
}
fn process_run(&self, cmd: &ProcessCommand) -> Result<ProcessOutput, String> {
let (code, chunks) = crate::bindings::moonlit::plugin::process::run(&to_wit_command(cmd))?;
Ok(ProcessOutput {
exit_code: code,
chunks: chunks.into_iter().map(from_wit_chunk).collect(),
})
}
fn process_spawn(&self, cmd: &ProcessCommand) -> Result<Box<dyn ChildHandle>, String> {
let child = crate::bindings::moonlit::plugin::process::spawn(&to_wit_command(cmd))?;
Ok(Box::new(RealChild { child }))
}
fn http_send(&self, req: &HttpRequestData) -> Result<HttpResponseData, String> {
http_send_impl(req)
}
fn env_var(&self, name: &str) -> Option<String> {
std::env::var(name).ok()
}
fn env_vars(&self) -> Vec<(String, String)> {
std::env::vars().collect()
}
fn random_bytes(&self, n: usize) -> Vec<u8> {
crate::bindings::wasi::random::random::get_random_bytes(n as u64)
}
fn monotonic_nanos(&self) -> u64 {
crate::bindings::wasi::clocks::monotonic_clock::now()
}
fn sleep_nanos(&self, nanos: u64) {
crate::bindings::wasi::clocks::monotonic_clock::subscribe_duration(nanos).block();
}
}
#[must_use]
fn to_wit_command(cmd: &ProcessCommand) -> crate::bindings::moonlit::plugin::process::Command {
crate::bindings::moonlit::plugin::process::Command {
program: cmd.program.clone(),
args: cmd.args.clone(),
cwd: cmd.cwd.clone(),
env: cmd.env.clone(),
stdin: cmd.stdin.clone(),
}
}
#[must_use]
fn from_wit_chunk(c: crate::bindings::moonlit::plugin::process::OutputChunk) -> OutputChunk {
let stream = match c.stream {
crate::bindings::moonlit::plugin::process::StdioStream::Stdout => StdioStream::Stdout,
crate::bindings::moonlit::plugin::process::StdioStream::Stderr => StdioStream::Stderr,
};
OutputChunk { stream, text: c.line }
}
struct RealChild {
child: crate::bindings::moonlit::plugin::process::Child,
}
impl ChildHandle for RealChild {
fn next_line(&mut self) -> Option<OutputChunk> {
self.child.next_line().map(from_wit_chunk)
}
fn wait(&mut self) -> i32 {
self.child.wait()
}
fn kill(&mut self) {
self.child.kill();
}
}
fn http_send_impl(req: &HttpRequestData) -> Result<HttpResponseData, String> {
use crate::bindings::wasi::http::outgoing_handler;
use crate::bindings::wasi::http::types::{Fields, Method, OutgoingBody, OutgoingRequest, RequestOptions, Scheme};
use crate::bindings::wasi::io::streams::StreamError;
let header_entries: Vec<(String, Vec<u8>)> = req.headers.iter().map(|(k, v)| (k.clone(), v.as_bytes().to_vec())).collect();
let fields = Fields::from_list(&header_entries).map_err(|e| format!("headers: {e:?}"))?;
let request = OutgoingRequest::new(fields);
let method = match req.method {
HttpMethod::Get => Method::Get,
HttpMethod::Post => Method::Post,
HttpMethod::Put => Method::Put,
HttpMethod::Patch => Method::Patch,
HttpMethod::Delete => Method::Delete,
};
request.set_method(&method).map_err(|()| "set_method".to_string())?;
let scheme = if req.scheme == "http" { Scheme::Http } else { Scheme::Https };
request.set_scheme(Some(&scheme)).map_err(|()| "set_scheme".to_string())?;
request
.set_authority(Some(&req.authority))
.map_err(|()| "set_authority".to_string())?;
request
.set_path_with_query(Some(&req.path_with_query))
.map_err(|()| "set_path".to_string())?;
let outgoing_body = request.body().map_err(|()| "body()".to_string())?;
if let Some(bytes) = &req.body {
let stream = outgoing_body.write().map_err(|()| "body.write()".to_string())?;
for chunk in bytes.chunks(4096) {
stream.blocking_write_and_flush(chunk).map_err(|e| format!("write: {e:?}"))?;
}
drop(stream);
}
OutgoingBody::finish(outgoing_body, None).map_err(|e| format!("body.finish: {e:?}"))?;
let options = if let Some(ms) = req.timeout_ms {
let opts = RequestOptions::new();
let ns = ms.saturating_mul(1_000_000);
opts
.set_connect_timeout(Some(ns))
.map_err(|()| "set_connect_timeout".to_string())?;
opts
.set_first_byte_timeout(Some(ns))
.map_err(|()| "set_first_byte_timeout".to_string())?;
Some(opts)
} else {
None
};
let future = outgoing_handler::handle(request, options).map_err(|e| format!("handle: {e:?}"))?;
let pollable = future.subscribe();
pollable.block();
let response = future
.get()
.ok_or_else(|| "future not ready".to_string())?
.map_err(|()| "future already taken".to_string())?
.map_err(|e| format!("response: {e:?}"))?;
let status = response.status();
let headers = response
.headers()
.entries()
.into_iter()
.map(|(k, v)| (k, String::from_utf8_lossy(&v).into_owned()))
.collect();
let incoming_body = response.consume().map_err(|()| "consume".to_string())?;
let body_stream = incoming_body.stream().map_err(|()| "body.stream".to_string())?;
let mut body = Vec::new();
loop {
match body_stream.blocking_read(8192) {
Ok(chunk) => body.extend_from_slice(&chunk),
Err(StreamError::Closed) => break,
Err(e) => return Err(format!("body read: {e:?}")),
}
}
Ok(HttpResponseData { status, headers, body })
}