use std::collections::HashMap;
use std::sync::Arc as Rc;
use aver_rt::{AverList, Header, HttpResponse};
use crate::nan_value::{Arena, NanValue, NanValueConvert};
use crate::value::{RuntimeError, Value, list_from_vec, list_view};
pub fn register(global: &mut HashMap<String, Value>) {
let mut members = HashMap::new();
for method in &["get", "head", "delete", "post", "put", "patch"] {
members.insert(
method.to_string(),
Value::Builtin(format!("Http.{}", method)),
);
}
global.insert(
"Http".to_string(),
Value::Namespace {
name: "Http".to_string(),
members,
},
);
}
pub const DECLARED_EFFECTS: &[&str] = &[
"Http.get",
"Http.head",
"Http.delete",
"Http.post",
"Http.put",
"Http.patch",
];
pub fn effects(name: &str) -> &'static [&'static str] {
match name {
"Http.get" => &["Http.get"],
"Http.head" => &["Http.head"],
"Http.delete" => &["Http.delete"],
"Http.post" => &["Http.post"],
"Http.put" => &["Http.put"],
"Http.patch" => &["Http.patch"],
_ => &[],
}
}
pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
match name {
"Http.get" | "Http.head" | "Http.delete" => Some(call_simple(name, args)),
"Http.post" | "Http.put" | "Http.patch" => Some(call_with_body(name, args)),
_ => None,
}
}
fn call_simple(name: &str, args: &[Value]) -> Result<Value, RuntimeError> {
if args.len() != 1 {
return Err(RuntimeError::Error(format!(
"Http.{}() takes 1 argument (url), got {}",
name.trim_start_matches("Http."),
args.len()
)));
}
let url = str_arg(&args[0], "Http: url must be a String")?;
let result = match name {
"Http.get" => aver_rt::http::get(&url),
"Http.head" => aver_rt::http::head(&url),
"Http.delete" => aver_rt::http::delete(&url),
_ => unreachable!(),
};
response_value(result)
}
fn call_with_body(name: &str, args: &[Value]) -> Result<Value, RuntimeError> {
if args.len() != 4 {
return Err(RuntimeError::Error(format!(
"Http.{}() takes 4 arguments (url, body, contentType, headers), got {}",
name.trim_start_matches("Http."),
args.len()
)));
}
let url = str_arg(&args[0], "Http: url must be a String")?;
let body = str_arg(&args[1], "Http: body must be a String")?;
let content_type = str_arg(&args[2], "Http: contentType must be a String")?;
let extra_headers = parse_request_headers(&args[3])?;
let result = match name {
"Http.post" => aver_rt::http::post(&url, &body, &content_type, &extra_headers),
"Http.put" => aver_rt::http::put(&url, &body, &content_type, &extra_headers),
"Http.patch" => aver_rt::http::patch(&url, &body, &content_type, &extra_headers),
_ => unreachable!(),
};
response_value(result)
}
fn str_arg(val: &Value, msg: &str) -> Result<String, RuntimeError> {
match val {
Value::Str(s) => Ok(s.clone()),
_ => Err(RuntimeError::Error(msg.to_string())),
}
}
fn parse_request_headers(val: &Value) -> Result<AverList<Header>, RuntimeError> {
let items = list_view(val)
.ok_or_else(|| RuntimeError::Error("Http: headers must be a List".to_string()))?;
let mut out = Vec::new();
for item in items.iter() {
let fields = match item {
Value::Record { fields, .. } => fields,
_ => {
return Err(RuntimeError::Error(
"Http: each header must be a record with 'name' and 'value' String fields"
.to_string(),
));
}
};
let get = |key: &str| -> Result<String, RuntimeError> {
fields
.iter()
.find(|(k, _)| k == key)
.and_then(|(_, v)| {
if let Value::Str(s) = v {
Some(s.clone())
} else {
None
}
})
.ok_or_else(|| {
RuntimeError::Error(format!(
"Http: header record must have a '{}' String field",
key
))
})
};
out.push(Header::from_strings(get("name")?, get("value")?));
}
Ok(AverList::from_vec(out))
}
fn response_value(result: Result<HttpResponse, String>) -> Result<Value, RuntimeError> {
match result {
Ok(resp) => Ok(Value::Ok(Box::new(http_response_to_value(resp)))),
Err(e) => Ok(Value::Err(Box::new(Value::Str(e)))),
}
}
pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
let methods = &["get", "head", "delete", "post", "put", "patch"];
let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
for method in methods {
let idx = arena.push_builtin(&format!("Http.{}", method));
members.push((Rc::from(*method), NanValue::new_builtin(idx)));
}
let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
name: Rc::from("Http"),
members,
});
global.insert("Http".to_string(), NanValue::new_namespace(ns_idx));
}
pub fn call_nv(
name: &str,
args: &[NanValue],
arena: &mut Arena,
) -> Option<Result<NanValue, RuntimeError>> {
if !matches!(
name,
"Http.get" | "Http.head" | "Http.delete" | "Http.post" | "Http.put" | "Http.patch"
) {
return None;
}
let old_args: Vec<Value> = args.iter().map(|nv| nv.to_value(arena)).collect();
let result = call(name, &old_args)?;
Some(result.map(|v| NanValue::from_value(&v, arena)))
}
fn http_response_to_value(resp: HttpResponse) -> Value {
let headers = resp
.headers
.into_iter()
.map(|header| Value::Record {
type_name: "Header".to_string(),
fields: vec![
("name".to_string(), Value::Str(header.name.to_string())),
("value".to_string(), Value::Str(header.value.to_string())),
]
.into(),
})
.collect();
Value::Record {
type_name: "HttpResponse".to_string(),
fields: vec![
("status".to_string(), Value::Int(resp.status)),
("body".to_string(), Value::Str(resp.body.to_string())),
("headers".to_string(), list_from_vec(headers)),
]
.into(),
}
}