lua-astra 0.25.0

🔥 Blazingly Fast 🔥 web server runtime for Lua
use crate::components::BodyLua;
use mlua::{LuaSerdeExt, UserData};
use reqwest::{Client, RequestBuilder};
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub struct HTTPClientRequest {
    pub url: String,
    pub method: String,
    pub headers: HashMap<String, String>,
    pub body: Option<String>,
    pub body_json: Option<serde_json::Value>,
    pub body_file: Option<String>,
    pub form: HashMap<String, String>,
}
impl HTTPClientRequest {
    pub fn register_to_lua(lua: &mlua::Lua) -> mlua::Result<()> {
        let function = lua.create_function(|_, url: String| {
            Ok(Self {
                url,
                method: "GET".to_string(),
                headers: HashMap::new(),
                body: None,
                body_json: None,
                body_file: None,
                form: HashMap::new(),
            })
        })?;
        lua.globals().set("astra_internal__http_request", function)
    }

    pub async fn request_builder(&self) -> RequestBuilder {
        let mut client = match self.method.to_uppercase().as_str() {
            "POST" => Client::new().post(&self.url),
            "PATCH" => Client::new().patch(&self.url),
            "PUT" => Client::new().put(&self.url),
            "DELETE" => Client::new().delete(&self.url),
            "HEAD" => Client::new().head(&self.url),
            _ => Client::new().get(&self.url),
        };

        client = if let Some(body) = &self.body {
            client.body(body.clone())
        } else if let Some(body) = &self.body_json {
            client.json(&body)
        } else if let Some(body) = &self.body_file {
            let path = std::path::PathBuf::from(body);
            let path_filename = path.clone();

            let file_form = reqwest::multipart::Form::new();
            if let Ok(file_form) = file_form
                .file(
                    if let Some(filename) = path_filename
                        .file_name()
                        .and_then(|filename| filename.to_str())
                    {
                        filename.to_string()
                    } else {
                        "file.txt".to_string()
                    },
                    path,
                )
                .await
            {
                client.multipart(file_form)
            } else {
                client
            }
        } else {
            client
        };

        if !self.headers.is_empty() {
            for (key, value) in self.headers.iter() {
                client = client.header(key, value);
            }
        }

        if !self.form.is_empty() {
            client = client.form(&self.form);
        }

        client
    }

    pub async fn response_to_http_client_response(
        response: reqwest::Response,
    ) -> HTTPClientResponse {
        let url = response.url().to_string();
        let status_code = response.status().as_u16();
        let remote_address = response.remote_addr().map(|i| i.to_string());
        let headers = response
            .headers()
            .iter()
            .map(|(key, value)| {
                (
                    key.to_string(),
                    String::from_utf8_lossy(value.as_bytes()).to_string(),
                )
            })
            .collect::<std::collections::HashMap<String, String>>();

        let body = if let Ok(bytes) = response.bytes().await {
            BodyLua::new(bytes)
        } else {
            BodyLua::new(bytes::Bytes::new())
        };

        HTTPClientResponse {
            url,
            status_code,
            remote_address,
            body,
            headers,
        }
    }
}
impl UserData for HTTPClientRequest {
    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("set_method", |_, this, method: String| {
            let mut request = this.clone();
            request.method = method;

            Ok(request)
        });

        methods.add_method_mut("set_header", |_, this, (key, value): (String, String)| {
            let mut request = this.clone();
            request.headers.insert(key, value);

            Ok(request)
        });

        methods.add_method_mut(
            "set_headers",
            |_, this, headers: HashMap<String, String>| {
                let mut request = this.clone();
                request.headers = headers;

                Ok(request)
            },
        );

        methods.add_method_mut("set_form", |_, this, (key, value): (String, String)| {
            let mut request = this.clone();
            request.form.insert(key, value);

            Ok(request)
        });

        methods.add_method_mut("set_forms", |_, this, form: HashMap<String, String>| {
            let mut request = this.clone();
            request.form = form;

            Ok(request)
        });

        methods.add_method_mut("set_body", |_, this, body: String| {
            let mut request = this.clone();
            request.body = Some(body);

            Ok(request)
        });

        methods.add_method_mut("set_json", |lua, this, body: mlua::Value| {
            let mut request = this.clone();
            request.body_json = lua.from_value::<serde_json::Value>(body).ok();

            Ok(request)
        });

        methods.add_method_mut("set_file", |_, this, file_path: String| {
            let mut request = this.clone();
            request.body_file = Some(file_path);

            Ok(request)
        });

        methods.add_async_method("execute", |_, this, ()| async move {
            let request = this.request_builder().await;
            match request.send().await {
                Ok(response) => Ok(Self::response_to_http_client_response(response).await),
                Err(e) => Err(mlua::Error::runtime(format!(
                    "HTTP Request did not execute successfully: {e}"
                ))),
            }
        });

        methods.add_async_method(
            "execute_task",
            |_, this, callback: mlua::Function| async move {
                tokio::spawn(async move {
                    let request = this.request_builder().await;
                    match request.send().await {
                        Ok(response) => {
                            if let Err(e) = callback
                                .call::<()>(Self::response_to_http_client_response(response).await)
                            {
                                println!("Error running a task: {e}");
                            }
                        }
                        Err(e) => eprintln!("HTTP Request did not execute successfully: {e}"),
                    };
                });

                Ok(())
            },
        );
    }
}

pub struct HTTPClientResponse {
    pub url: String,
    pub status_code: u16,
    pub remote_address: Option<String>,
    pub body: BodyLua,
    pub headers: HashMap<String, String>,
}
impl UserData for HTTPClientResponse {
    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("url", |_, this, ()| Ok(this.url.clone()));
        methods.add_method("status_code", |_, this, ()| Ok(this.status_code));
        methods.add_method("remote_address", |_, this, ()| {
            Ok(this.remote_address.clone())
        });
        methods.add_method("body", |_, this, ()| Ok(this.body.clone()));
        methods.add_method("headers", |_, this, ()| Ok(this.headers.clone()));
    }
}