crows_bindings/
lib.rs

1use std::{cell::RefCell, collections::HashMap};
2
3pub use crows_macros::config;
4pub use crows_shared::Config as ExecutorConfig;
5pub use crows_shared::ConstantArrivalRateConfig;
6use serde::{de::DeserializeOwned, Deserialize, Serialize};
7use serde_json::{from_slice, to_vec};
8
9#[derive(Serialize, Deserialize, PartialEq, Debug)]
10pub enum HTTPMethod {
11    HEAD,
12    GET,
13    POST,
14    PUT,
15    DELETE,
16    OPTIONS,
17}
18
19#[derive(Serialize, Deserialize, PartialEq, Debug)]
20pub struct HTTPRequest {
21    // TODO: these should not be public I think, I'd prefer to do a public interface for them
22    pub url: String,
23    pub method: HTTPMethod,
24    pub headers: HashMap<String, String>,
25    pub body: Option<String>,
26}
27
28#[derive(Debug, Deserialize, Serialize)]
29pub struct HTTPError {
30    pub message: String,
31}
32
33#[derive(Serialize, Deserialize, PartialEq, Debug)]
34pub struct HTTPResponse {
35    // TODO: these should not be public I think, I'd prefer to do a public interface for them
36    pub headers: HashMap<String, String>,
37    pub body: String,
38    pub status: u16,
39}
40
41fn extract_from_return_value(value: u64) -> (u8, u32, u32) {
42    let status = ((value >> 56) & 0xFF) as u8;
43    let length = ((value >> 32) & 0x00FFFFFF) as u32;
44    let ptr = (value & 0xFFFFFFFF) as u32;
45    (status, length, ptr)
46}
47
48mod bindings {
49    #[link(wasm_import_module = "crows")]
50    extern "C" {
51        pub fn log(content: *mut u8, content_len: usize);
52        pub fn http(content: *mut u8, content_len: usize) -> u64;
53        pub fn consume_buffer(index: u32, content: *mut u8, content_len: usize);
54        pub fn set_config(content: *mut u8, content_len: usize) -> u32;
55    }
56}
57
58fn with_buffer<R>(f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
59    // using a buffer saved in thread_local allows us to share it between function calls
60    thread_local! {
61        static BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(1024));
62    }
63
64    BUFFER.with(|r| {
65        let mut buf = r.borrow_mut();
66        buf.clear();
67        f(&mut buf)
68    })
69}
70
71pub fn http_request(
72    url: String,
73    method: HTTPMethod,
74    headers: HashMap<String, String>,
75    body: String,
76) -> Result<HTTPResponse, HTTPError> {
77    let body = Some(body);
78    let request = HTTPRequest {
79        method,
80        url,
81        headers,
82        body,
83    };
84
85    call_host_function(&request, |buf| unsafe {
86        bindings::http(buf.as_mut_ptr(), buf.len())
87    })
88}
89
90fn call_host_function<T, R, E>(arguments: &T, f: impl FnOnce(&mut Vec<u8>) -> u64) -> Result<R, E>
91where
92    T: Serialize,
93    R: DeserializeOwned,
94    E: DeserializeOwned,
95{
96    let mut encoded = to_vec(arguments).unwrap();
97
98    let response = f(&mut encoded);
99
100    let (status, length, index) = extract_from_return_value(response);
101
102    let mut buf = encoded;
103    buf.clear();
104
105    // when using reserve_exact it guarantees capacity to be vector.len() + additional long,
106    // thus we can just use length for reserving
107    buf.reserve_exact(length as usize);
108
109    unsafe {
110        bindings::consume_buffer(index, buf.as_mut_ptr(), length as usize);
111        buf.set_len(length as usize);
112    }
113
114    if status == 0 {
115        Ok(from_slice(&buf).expect("Couldn't decode message from the host"))
116    } else {
117        Err(from_slice(&buf).expect("Couldn't decode message from the host"))
118    }
119}
120
121pub fn __set_config(config: ExecutorConfig) -> u32 {
122    let mut encoded = to_vec(&config).unwrap();
123    unsafe { bindings::set_config(encoded.as_mut_ptr(), encoded.len()) }
124}