1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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" => {
            // TODO: When it reaches here it panics for some reason. Grave.
            //let json: JsonValue = res.json().wait()
            //    .map_err(|err| LuaError::external(err))?;
            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)
}

/// For POST and PUT requests.
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(())
}