use bytes::Bytes;
use golem_wasm_ast::analysis::analysed_type::{
case, field, list, option, record, str, tuple, u16, u8, unit_case, variant,
};
use golem_wasm_ast::analysis::AnalysedType;
use golem_wasm_ast::analysis::{AnalysedExport, AnalysedFunction, AnalysedInstance};
use golem_wasm_rpc::{Value, ValueAndType};
use std::sync::LazyLock;
pub static PARSED_FUNCTION_NAME: LazyLock<rib::ParsedFunctionName> =
LazyLock::new(|| rib::ParsedFunctionName {
site: rib::ParsedFunctionSite::PackagedInterface {
namespace: "golem".to_string(),
package: "http".to_string(),
interface: "incoming-handler".to_string(),
version: None,
},
function: rib::ParsedFunctionReference::Function {
function: "handle".to_string(),
},
});
pub static ANALYZED_FUNCTION_PARAMETERS: LazyLock<
Vec<golem_wasm_ast::analysis::AnalysedFunctionParameter>,
> = {
use golem_wasm_ast::analysis::*;
LazyLock::new(|| {
vec![AnalysedFunctionParameter {
name: "request".to_string(),
typ: IncomingHttpRequest::analysed_type(),
}]
})
};
pub static ANALYZED_FUNCTION_RESULT: LazyLock<
Option<golem_wasm_ast::analysis::AnalysedFunctionResult>,
> = {
use golem_wasm_ast::analysis::*;
LazyLock::new(|| {
Some(AnalysedFunctionResult {
typ: HttpResponse::analysed_type(),
})
})
};
pub static ANALYZED_FUNCTION: LazyLock<AnalysedFunction> = {
use golem_wasm_ast::analysis::*;
LazyLock::new(|| AnalysedFunction {
name: "handle".to_string(),
parameters: ANALYZED_FUNCTION_PARAMETERS.clone(),
result: ANALYZED_FUNCTION_RESULT.clone(),
})
};
pub const FUNCTION_NAME: &str = "golem:http/incoming-handler.{handle}";
pub static ANALYZED_EXPORT: LazyLock<AnalysedExport> = LazyLock::new(|| {
AnalysedExport::Instance(AnalysedInstance {
name: "golem:http/incoming-handler".to_string(),
functions: vec![ANALYZED_FUNCTION.clone()],
})
});
pub fn implements_required_interfaces(exports: &[AnalysedExport]) -> bool {
let compatible_interfaces = [
"wasi:http/incoming-handler@0.2.0".to_string(),
"wasi:http/incoming-handler@0.2.1".to_string(),
"wasi:http/incoming-handler@0.2.2".to_string(),
"wasi:http/incoming-handler@0.2.3".to_string(),
];
exports.iter().any(|ae| match ae {
AnalysedExport::Instance(AnalysedInstance { name, .. }) => {
compatible_interfaces.contains(name)
}
_ => false,
})
}
macro_rules! extract {
($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?, $ret:expr, $err:expr) => {
match $expression {
$pattern $(if $guard)? => Ok($ret),
_ => Err($err)
}
};
}
pub enum HttpScheme {
HTTP,
HTTPS,
Custom(String),
}
impl HttpScheme {
pub fn analyzed_type() -> AnalysedType {
variant(vec![
unit_case("HTTP"),
unit_case("HTTPS"),
case("custom", str()),
])
.named("HttpScheme")
}
pub fn from_value(value: &Value) -> Result<Self, String> {
let (case_idx, case_value) = extract!(
value,
Value::Variant {
case_idx,
case_value
},
(case_idx, case_value),
"not a variant"
)?;
match case_idx {
0 => Ok(Self::HTTP),
1 => Ok(Self::HTTPS),
2 => {
let value = case_value.as_ref().ok_or("no case_value provided")?;
let custom_method =
extract!(*value.clone(), Value::String(inner), inner, "not a string")?;
Ok(Self::Custom(custom_method))
}
_ => Err("unknown case")?,
}
}
pub fn to_value(self) -> Value {
match self {
Self::HTTP => Value::Variant {
case_idx: 0,
case_value: None,
},
Self::HTTPS => Value::Variant {
case_idx: 1,
case_value: None,
},
Self::Custom(custom_method) => Value::Variant {
case_idx: 2,
case_value: Some(Box::new(Value::String(custom_method))),
},
}
}
}
impl From<http::uri::Scheme> for HttpScheme {
fn from(value: http::uri::Scheme) -> Self {
match value {
well_known if well_known == http::uri::Scheme::HTTP => Self::HTTP,
well_known if well_known == http::uri::Scheme::HTTPS => Self::HTTPS,
other => Self::Custom(other.to_string()),
}
}
}
pub enum HttpMethod {
GET,
HEAD,
POST,
PUT,
DELETE,
CONNECT,
OPTIONS,
TRACE,
PATCH,
Custom(String),
}
impl HttpMethod {
pub fn analyzed_type() -> AnalysedType {
variant(vec![
unit_case("GET"),
unit_case("HEAD"),
unit_case("POST"),
unit_case("PUT"),
unit_case("DELETE"),
unit_case("CONNECT"),
unit_case("OPTIONS"),
unit_case("TRACE"),
unit_case("PATCH"),
case("custom", str()),
])
.named("HttpMethod")
}
pub fn from_value(value: &Value) -> Result<Self, String> {
let (case_idx, case_value) = extract!(
value,
Value::Variant {
case_idx,
case_value
},
(case_idx, case_value),
"not a variant"
)?;
match case_idx {
0 => Ok(Self::GET),
1 => Ok(Self::HEAD),
2 => Ok(Self::POST),
3 => Ok(Self::PUT),
4 => Ok(Self::DELETE),
5 => Ok(Self::CONNECT),
6 => Ok(Self::OPTIONS),
7 => Ok(Self::TRACE),
8 => Ok(Self::PATCH),
9 => {
let value = case_value.as_ref().ok_or("no case_value provided")?;
let custom_method =
extract!(*value.clone(), Value::String(inner), inner, "not a string")?;
Ok(Self::Custom(custom_method))
}
_ => Err("unknown case")?,
}
}
pub fn to_value(self) -> Value {
match self {
Self::GET => Value::Variant {
case_idx: 0,
case_value: None,
},
Self::HEAD => Value::Variant {
case_idx: 1,
case_value: None,
},
Self::POST => Value::Variant {
case_idx: 2,
case_value: None,
},
Self::PUT => Value::Variant {
case_idx: 3,
case_value: None,
},
Self::DELETE => Value::Variant {
case_idx: 4,
case_value: None,
},
Self::CONNECT => Value::Variant {
case_idx: 5,
case_value: None,
},
Self::OPTIONS => Value::Variant {
case_idx: 6,
case_value: None,
},
Self::TRACE => Value::Variant {
case_idx: 7,
case_value: None,
},
Self::PATCH => Value::Variant {
case_idx: 8,
case_value: None,
},
Self::Custom(custom_method) => Value::Variant {
case_idx: 9,
case_value: Some(Box::new(Value::String(custom_method))),
},
}
}
pub fn from_http_method(value: http::Method) -> Self {
use http::Method as M;
match value {
M::GET => Self::GET,
M::CONNECT => Self::CONNECT,
M::DELETE => Self::DELETE,
M::HEAD => Self::HEAD,
M::OPTIONS => Self::OPTIONS,
M::PATCH => Self::PATCH,
M::POST => Self::POST,
M::PUT => Self::PUT,
M::TRACE => Self::TRACE,
other => Self::Custom(other.to_string()),
}
}
}
impl TryInto<http::Method> for HttpMethod {
type Error = http::method::InvalidMethod;
fn try_into(self) -> Result<http::Method, Self::Error> {
match self {
Self::GET => Ok(http::Method::GET),
Self::HEAD => Ok(http::Method::HEAD),
Self::POST => Ok(http::Method::POST),
Self::PUT => Ok(http::Method::PUT),
Self::DELETE => Ok(http::Method::DELETE),
Self::CONNECT => Ok(http::Method::CONNECT),
Self::OPTIONS => Ok(http::Method::OPTIONS),
Self::TRACE => Ok(http::Method::TRACE),
Self::PATCH => Ok(http::Method::PATCH),
Self::Custom(method) => http::Method::from_bytes(method.as_bytes()),
}
}
}
pub struct HttpFields(pub Vec<(String, Bytes)>);
impl HttpFields {
pub fn analyzed_type() -> AnalysedType {
list(tuple(vec![str(), list(u8())])).named("HttpFields")
}
pub fn from_value(value: &Value) -> Result<Self, String> {
let mut result = Vec::new();
let list_values = extract!(value, Value::List(inner), inner, "not a list")?;
for lv in list_values {
let tuple_value = extract!(lv, Value::Tuple(inner), inner, "not a tuple")?;
let (name, values) = extract!(
tuple_value.as_slice(),
[Value::String(name), Value::List(values)],
(name.clone(), values),
"incompatible types"
)?;
let mut result_value = Vec::new();
for v in values {
let v = extract!(v, Value::U8(inner), *inner, "not a byte")?;
result_value.push(v);
}
result.push((name, Bytes::from(result_value)));
}
Ok(HttpFields(result))
}
pub fn to_value(self) -> Value {
let mut list_values = Vec::new();
for (name, value) in self.0 {
let converted_bytes: Vec<Value> = value.into_iter().map(Value::U8).collect::<Vec<_>>();
list_values.push(Value::Tuple(vec![
Value::String(name),
Value::List(converted_bytes),
]));
}
Value::List(list_values)
}
}
pub struct HttpBodyContent(pub Bytes);
impl HttpBodyContent {
pub fn analyzed_type() -> AnalysedType {
list(u8()).named("HttpBodyContent")
}
pub fn from_value(value: &Value) -> Result<Self, String> {
let mut result = Vec::new();
let list_values = extract!(value, Value::List(inner), inner, "not a list")?;
for lv in list_values {
let byte_value = extract!(lv, Value::U8(inner), *inner, "not a byte")?;
result.push(byte_value);
}
Ok(HttpBodyContent(Bytes::from(result)))
}
pub fn to_value(self) -> Value {
let converted = self.0.into_iter().map(Value::U8).collect::<Vec<_>>();
Value::List(converted)
}
}
pub struct HttpBodyAndTrailers {
pub content: HttpBodyContent,
pub trailers: Option<HttpFields>,
}
impl HttpBodyAndTrailers {
pub fn analysed_type() -> AnalysedType {
record(vec![
field("content", HttpBodyContent::analyzed_type()),
field("trailers", option(HttpFields::analyzed_type())),
])
.named("HttpBodyAndTrailers")
}
pub fn from_value(value: &Value) -> Result<Self, String> {
let record_values = extract!(value, Value::Record(inner), inner, "not a record")?;
if record_values.len() != 2 {
Err("wrong length of record data")?;
};
let content = HttpBodyContent::from_value(&record_values[0])?;
let trailers = extract!(
&record_values[1],
Value::Option(inner),
match inner {
Some(inner) => Some(HttpFields::from_value(inner)?),
None => None,
},
"not an option"
)?;
Ok(HttpBodyAndTrailers { content, trailers })
}
pub fn to_value(self) -> Value {
let converted_content = self.content.to_value();
let converted_trailers = Value::Option(self.trailers.map(|t| Box::new(t.to_value())));
Value::Record(vec![converted_content, converted_trailers])
}
}
pub struct IncomingHttpRequest {
pub method: HttpMethod,
pub scheme: HttpScheme,
pub authority: String,
pub path_and_query: String,
pub headers: HttpFields,
pub body: Option<HttpBodyAndTrailers>,
}
impl IncomingHttpRequest {
pub fn analysed_type() -> AnalysedType {
record(vec![
field("method", HttpMethod::analyzed_type()),
field("scheme", HttpScheme::analyzed_type()),
field("authority", str()),
field("path-and-query", str()),
field("headers", HttpFields::analyzed_type()),
field(
"body-and-trailers",
option(HttpBodyAndTrailers::analysed_type()),
),
])
.named("IncomingHttpRequest")
}
pub fn from_function_input(inputs: &[Value]) -> Result<Self, String> {
if inputs.len() != 1 {
Err("invalid number of inputs")?;
};
Self::from_value(&inputs[0])
.map_err(|e| format!("Failed parsing input as http request: {e}"))
}
fn from_value(value: &Value) -> Result<Self, String> {
let record_values = extract!(value, Value::Record(inner), inner, "not a record")?;
if record_values.len() != 6 {
Err("wrong length of record data")?;
};
let method = HttpMethod::from_value(&record_values[0])?;
let scheme = HttpScheme::from_value(&record_values[1])?;
let authority = extract!(
record_values[2].clone(),
Value::String(inner),
inner,
"not a string"
)?;
let path_and_query = extract!(
record_values[3].clone(),
Value::String(inner),
inner,
"not a string"
)?;
let headers = HttpFields::from_value(&record_values[4])?;
let body = extract!(
&record_values[5],
Value::Option(inner),
match inner {
Some(v) => Some(HttpBodyAndTrailers::from_value(v)?),
None => None,
},
"not an option"
)?;
Ok(IncomingHttpRequest {
method,
scheme,
authority,
path_and_query,
headers,
body,
})
}
pub fn to_value(self) -> Value {
Value::Record(vec![
self.method.to_value(),
self.scheme.to_value(),
Value::String(self.authority),
Value::String(self.path_and_query),
self.headers.to_value(),
Value::Option(self.body.map(|b| Box::new(b.to_value()))),
])
}
}
pub struct HttpResponse {
pub status: u16,
pub headers: HttpFields,
pub body: Option<HttpBodyAndTrailers>,
}
impl HttpResponse {
pub fn analysed_type() -> AnalysedType {
record(vec![
field("status", u16()),
field("headers", HttpFields::analyzed_type()),
field(
"body-and-trailers",
option(HttpBodyAndTrailers::analysed_type()),
),
])
.named("HttpResponse")
}
pub fn from_value(value: Value) -> Result<Self, String> {
let record_values = extract!(value, Value::Record(inner), inner, "not a record")?;
if record_values.len() != 3 {
Err("wrong length of record data")?;
};
let status = extract!(
record_values[0].clone(),
Value::U16(inner),
inner,
"not a u16"
)?;
let headers = HttpFields::from_value(&record_values[1])?;
let body = extract!(
&record_values[2],
Value::Option(inner),
inner.as_ref(),
"not an option"
)?;
let body = if let Some(b) = body {
Some(HttpBodyAndTrailers::from_value(b)?)
} else {
None
};
Ok(HttpResponse {
status,
headers,
body,
})
}
pub fn from_function_output(output: Option<ValueAndType>) -> Result<Self, String> {
let value: Value = output.map(|vnt| vnt.value).unwrap_or(Value::Record(vec![]));
let mut tuple_values = extract!(value, Value::Tuple(inner), inner, "not a tuple")?;
if tuple_values.len() != 1 {
Err("unexpected number of outputs")?
};
Self::from_value(tuple_values.remove(0))
}
pub fn to_value(self) -> Value {
let converted_status: Value = Value::U16(self.status);
let converted_headers: Value = self.headers.to_value();
let converted_body: Value = Value::Option(self.body.map(|b| Box::new(b.to_value())));
Value::Record(vec![converted_status, converted_headers, converted_body])
}
}