use actix_web::{
http::Method,
HttpMessage,
client::{ClientRequest, ClientRequestBuilder, ClientResponse}
};
use futures::Future;
use rlua::prelude::*;
use rlua_serde;
use serde_json::{self, Value as JsonValue};
use std::str::FromStr;
fn map_actix_err(err: actix_web::Error) -> LuaError {
LuaError::external(format_err!("actix_web error: {}", &err))
}
fn parse_response(lua: &Lua, res: ClientResponse) -> Result<LuaTable, LuaError> {
let body_data = res.body()
.wait()
.map_err(|err| {
LuaError::external(format_err!("Invalid body {}", err))
})?;
let body_string = String::from_utf8(body_data.iter().cloned().collect())
.map_err(|err| {
LuaError::external(format_err!("Invalid body {}", err))
})?;
let body = match res.content_type() {
"application/json" => {
let json: JsonValue = serde_json::from_str(&body_string)
.map_err(|err| LuaError::external(err))?;
rlua_serde::to_value(lua, json)
.map_err(|err| LuaError::external(err))
},
_ => {
rlua_serde::to_value(lua, body_string.clone())
}
}?;
let headers = lua.create_table()?;
for (key, value) in res.headers().iter() {
if let Ok(value) = value.to_str() {
headers.set(key.as_str(), value)?;
}
}
let lres = lua.create_table()?;
lres.set("status", res.status().as_u16())?;
lres.set("headers", headers)?;
lres.set("body", body)?;
lres.set("body_raw", body_string)?;
Ok(lres)
}
fn set_body(value: LuaValue, request_builder: &mut ClientRequestBuilder) -> Result<ClientRequest, LuaError> {
match value {
LuaValue::Table(_) => {
let json_value: JsonValue = rlua_serde::from_value(value)
.map_err(LuaError::external)?;
request_builder.json(&json_value).map_err(map_actix_err)
},
LuaValue::String(string) => {
let string = string.to_str()?.to_owned();
request_builder.body(&string).map_err(map_actix_err)
},
_ => Err(LuaError::external(format_err!("Unsupported POST body: {:?}", value))),
}
}
fn set_headers(value: LuaValue, request_builder: &mut ClientRequestBuilder) -> Result<(), LuaError> {
if let LuaValue::Table(headers) = value.clone() {
for pair in headers.pairs() {
let (key, value): (String, LuaValue) = pair?;
let value = match value {
LuaValue::String(value) => value.to_str()?.to_owned(),
LuaValue::Number(number) => number.to_string(),
LuaValue::Integer(number) => number.to_string(),
ref value @ _ => unimplemented!("Header value is not supported: {:?}", value),
};
request_builder.header(&key as &str, value);
}
} else {
return Err(LuaError::external(format_err!("Invalid client headers {:?}", &value)))
}
Ok(())
}
fn send_lua_request <'a> (lua: &'a Lua, val: LuaValue<'a>) -> Result<LuaTable<'a>, LuaError> {
let mut builder = ClientRequest::build();
let body = match val {
LuaValue::String(s) => {
builder.uri(s.to_str()?).method(Method::GET);
None
},
LuaValue::Table(table) => {
if let Some(method) = table.get::<_, Option<String>>("method")? {
builder.method(Method::from_str(&method.to_uppercase()).map_err(LuaError::external)?);
}
if let Some(uri) = table.get::<_, Option<String>>("uri")? {
builder.uri(&uri);
}
if let Some(headers) = table.get("headers")? {
set_headers(headers, &mut builder)?;
}
table.get("body")?
},
_ => {
return Err(LuaError::RuntimeError("Invalid arguments".to_string()))
}
};
let response = (match body {
Some(body) => set_body(body, &mut builder)?,
None => builder.finish().map_err(map_actix_err)?
}).send().wait().map_err(|err| {
LuaError::external(format_err!("Request failed: {}", err))
})?;
parse_response(lua, response)
}
pub fn init(lua: &Lua) -> Result<(), LuaError> {
let table = lua.create_table()?;
table.set("send", lua.create_function(send_lua_request)?)?;
lua.globals().set("client_request", table)?;
Ok(())
}