diff options
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..992147c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,155 @@ +use magnus::{define_module, function, method, Error, Module, Object, Value, class}; +use magnus::value::ReprValue; +use reqwest::{Client, Method, Response}; +use std::collections::HashMap; +use std::time::Duration; +use tokio::runtime::Runtime; + +#[magnus::wrap(class = "Net::Hippie::RustResponse")] +struct RustResponse { + status: u16, + headers: HashMap<String, String>, + body: String, +} + +impl RustResponse { + fn new(status: u16, headers: HashMap<String, String>, body: String) -> Self { + Self { + status, + headers, + body, + } + } + + fn code(&self) -> String { + self.status.to_string() + } + + fn body(&self) -> String { + self.body.clone() + } + + fn get_header(&self, name: String) -> Option<String> { + self.headers.get(&name.to_lowercase()).cloned() + } +} + +#[magnus::wrap(class = "Net::Hippie::RustClient")] +struct RustClient { + client: Client, + runtime: Runtime, +} + +impl RustClient { + fn new() -> Result<Self, Error> { + let client = Client::builder() + .timeout(Duration::from_secs(10)) + .connect_timeout(Duration::from_secs(10)) + .redirect(reqwest::redirect::Policy::none()) + .build() + .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?; + + let runtime = Runtime::new() + .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?; + + Ok(Self { client, runtime }) + } + + fn execute_request( + &self, + method_str: String, + url: String, + _headers: Value, // Simplified - ignore headers for now + body: String, + ) -> Result<RustResponse, Error> { + let method = match method_str.to_uppercase().as_str() { + "GET" => Method::GET, + "POST" => Method::POST, + "PUT" => Method::PUT, + "DELETE" => Method::DELETE, + "PATCH" => Method::PATCH, + _ => return Err(Error::new(magnus::exception::arg_error(), "Invalid HTTP method")), + }; + + self.runtime.block_on(async { + let mut request_builder = self.client.request(method, &url); + + // Add body if not empty + if !body.is_empty() { + request_builder = request_builder.body(body); + } + + let response = request_builder.send().await + .map_err(|e| self.map_reqwest_error(e))?; + + self.convert_response(response).await + }) + } + + async fn convert_response(&self, response: Response) -> Result<RustResponse, Error> { + let status = response.status().as_u16(); + + let mut headers = HashMap::new(); + for (key, value) in response.headers() { + if let Ok(value_str) = value.to_str() { + headers.insert(key.as_str().to_lowercase(), value_str.to_string()); + } + } + + let body = response.text().await + .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?; + + Ok(RustResponse::new(status, headers, body)) + } + + fn map_reqwest_error(&self, error: reqwest::Error) -> Error { + if error.is_timeout() { + Error::new(magnus::exception::runtime_error(), "Net::ReadTimeout") + } else if error.is_connect() { + Error::new(magnus::exception::runtime_error(), "Errno::ECONNREFUSED") + } else { + Error::new(magnus::exception::runtime_error(), error.to_string()) + } + } + + fn get(&self, url: String, headers: Value, body: String) -> Result<RustResponse, Error> { + self.execute_request("GET".to_string(), url, headers, body) + } + + fn post(&self, url: String, headers: Value, body: String) -> Result<RustResponse, Error> { + self.execute_request("POST".to_string(), url, headers, body) + } + + fn put(&self, url: String, headers: Value, body: String) -> Result<RustResponse, Error> { + self.execute_request("PUT".to_string(), url, headers, body) + } + + fn delete(&self, url: String, headers: Value, body: String) -> Result<RustResponse, Error> { + self.execute_request("DELETE".to_string(), url, headers, body) + } + + fn patch(&self, url: String, headers: Value, body: String) -> Result<RustResponse, Error> { + self.execute_request("PATCH".to_string(), url, headers, body) + } +} + +#[magnus::init] +fn init() -> Result<(), Error> { + let net_module = define_module("Net")?; + let hippie_module = net_module.define_module("Hippie")?; + + let rust_client_class = hippie_module.define_class("RustClient", class::object())?; + rust_client_class.define_singleton_method("new", function!(RustClient::new, 0))?; + rust_client_class.define_method("get", method!(RustClient::get, 3))?; + rust_client_class.define_method("post", method!(RustClient::post, 3))?; + rust_client_class.define_method("put", method!(RustClient::put, 3))?; + rust_client_class.define_method("delete", method!(RustClient::delete, 3))?; + rust_client_class.define_method("patch", method!(RustClient::patch, 3))?; + + let rust_response_class = hippie_module.define_class("RustResponse", class::object())?; + rust_response_class.define_method("code", method!(RustResponse::code, 0))?; + rust_response_class.define_method("body", method!(RustResponse::body, 0))?; + rust_response_class.define_method("[]", method!(RustResponse::get_header, 1))?; + + Ok(()) +}
\ No newline at end of file |
