-
-
Save fasterthanlime/0fedbf0b4a61053d4e7683eca05d99ea to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Allows declaring traits and implementing them for structs | |
//! in a single go. | |
#[macro_export] | |
macro_rules! mod_entry { | |
($mod_name:ident => $body:block) => { | |
#[cfg(feature = "impl")] | |
#[no_mangle] | |
pub extern "Rust" fn awaken() -> &'static (dyn Mod) { | |
fn awaken() -> impl Mod { | |
$body | |
} | |
let m = awaken(); | |
let m: Box<dyn Mod> = Box::new(m); | |
Box::leak(m) | |
} | |
#[cfg(not(feature = "impl"))] | |
pub fn load() -> &'static (dyn Mod) { | |
extern "C" { | |
fn dlopen(filename: *const i8, flag: i32) -> *mut std::ffi::c_void; | |
fn dlsym(handle: *mut std::ffi::c_void, symbol: *const i8) | |
-> *mut std::ffi::c_void; | |
fn dlerror() -> *mut i8; | |
} | |
static MOD: std::sync::LazyLock<&'static (dyn Mod)> = | |
::std::sync::LazyLock::new(|| { | |
let exe_path = std::env::current_exe().unwrap(); | |
let exe_folder = exe_path.parent().unwrap(); | |
let libmod_path = | |
exe_folder.join(format!("libmod_{}.so", stringify!($mod_name))); | |
unsafe { | |
let path_str = std::ffi::CString::new(libmod_path.to_str().unwrap()) | |
.expect("Invalid path"); | |
let handle = dlopen(path_str.as_ptr(), 1); // RTLD_LAZY = 1 | |
if handle.is_null() { | |
let err = dlerror(); | |
panic!( | |
"Failed to load module: {}", | |
std::ffi::CStr::from_ptr(err).to_string_lossy() | |
); | |
} | |
let symbol_name = std::ffi::CString::new("awaken").unwrap(); | |
let awaken_ptr = dlsym(handle, symbol_name.as_ptr()); | |
if awaken_ptr.is_null() { | |
let err = dlerror(); | |
panic!( | |
"Failed to load awaken symbol: {}", | |
std::ffi::CStr::from_ptr(err).to_string_lossy() | |
); | |
} | |
let awaken: unsafe extern "Rust" fn() -> &'static (dyn Mod) = | |
std::mem::transmute(awaken_ptr); | |
awaken() | |
} | |
}); | |
*MOD | |
} | |
}; | |
} | |
#[macro_export] | |
macro_rules! declare_trait_and_impl { | |
(impl $trait_name:ident for $struct_name:ident { | |
$(($($signature:tt)*) $body:block)* | |
}) => { | |
pub trait $trait_name: Send + Sync + 'static { | |
// method declarations go here | |
$( | |
$crate::function_header!($($signature)*); | |
)* | |
} | |
impl dyn $trait_name { | |
#[doc(hidden)] | |
fn _assert_dyn_compatible(self: Box<Self>) {} | |
} | |
#[cfg(feature = "impl")] | |
impl $trait_name for $struct_name { | |
// method implementations go here | |
$($($signature)* $body)* | |
} | |
}; | |
} | |
#[macro_export] | |
macro_rules! function_header { | |
(fn $name:ident(mut $($rest:tt)*) -> $ret:ty) => { | |
fn $name($($rest)*) -> $ret; | |
}; | |
(fn $name:ident($($rest:tt)*) -> $ret:ty) => { | |
fn $name($($rest)*) -> $ret; | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bytes::Bytes; | |
use conflux2::declare_trait_and_impl; | |
use futures_core::{future::BoxFuture, stream::BoxStream}; | |
use std::collections::HashMap; | |
#[derive(Debug)] | |
pub struct StatusCode(pub u16); | |
impl std::fmt::Display for StatusCode { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
write!(f, "{}", self.0) | |
} | |
} | |
#[derive(Debug)] | |
pub enum Error { | |
/// Any other error | |
Any(String), | |
/// JSON parsing error | |
Json(String), | |
/// HTTP error | |
Non200Status { | |
status: StatusCode, | |
response: String, | |
}, | |
} | |
#[derive(Clone, Copy, Debug)] | |
pub enum Method { | |
GET, | |
POST, | |
PUT, | |
DELETE, | |
HEAD, | |
} | |
#[derive(Clone)] | |
pub struct ClientOpts { | |
pub resolve_to_addrs: HashMap<String, Vec<std::net::SocketAddr>>, | |
pub follow_redirects: bool, | |
} | |
#[cfg(feature = "impl")] | |
struct ModImpl {} | |
declare_trait_and_impl! { | |
impl Mod for ModImpl { | |
(fn client(&self) -> Box<dyn Client>) { | |
Box::new(ClientImpl::new(None)) | |
} | |
(fn client_with_opts(&self, opts: ClientOpts) -> Box<dyn Client>) { | |
Box::new(ClientImpl::new(Some(opts))) | |
} | |
} | |
} | |
conflux2::mod_entry!(httpclient2 => { ModImpl {} }); | |
#[cfg(feature = "impl")] | |
struct ClientImpl { | |
client: reqwest::Client, | |
} | |
#[cfg(feature = "impl")] | |
impl ClientImpl { | |
fn new(opts: Option<ClientOpts>) -> Self { | |
let mut builder = reqwest::Client::builder(); | |
if let Some(opts) = opts { | |
for (host, addrs) in opts.resolve_to_addrs { | |
builder = builder.resolve_to_addrs(&host, &addrs); | |
} | |
if opts.follow_redirects { | |
builder = builder.redirect(reqwest::redirect::Policy::limited(10)); | |
} else { | |
builder = builder.redirect(reqwest::redirect::Policy::none()); | |
} | |
} | |
Self { | |
client: builder.build().unwrap(), | |
} | |
} | |
} | |
declare_trait_and_impl! { | |
impl Client for ClientImpl { | |
(fn request(&self, method: Method, url: &str) -> Box<dyn RequestBuilder>) { | |
Box::new(RequestBuilderImpl { | |
client: self.client.clone(), | |
method, | |
url: url.to_string(), | |
headers: Default::default(), | |
body: None, | |
form: None, | |
auth: None, | |
}) | |
} | |
} | |
} | |
#[cfg(feature = "impl")] | |
struct RequestBuilderImpl { | |
client: reqwest::Client, | |
method: Method, | |
url: String, | |
headers: Vec<(String, String)>, | |
body: Option<Bytes>, | |
form: Option<String>, | |
auth: Option<(String, Option<String>)>, | |
} | |
declare_trait_and_impl! { | |
impl RequestBuilder for RequestBuilderImpl { | |
(fn body(mut self: Box<Self>, body: Bytes) -> Box<dyn RequestBuilder>) { | |
self.body = Some(body); | |
self | |
} | |
(fn form(mut self: Box<Self>, form: String) -> Box<dyn RequestBuilder>) { | |
self.form = Some(form); | |
self.headers.push(("content-type".to_string(), "application/x-www-form-urlencoded".to_string())); | |
self | |
} | |
(fn header(mut self: Box<Self>, key: &str, value: &str) -> Box<dyn RequestBuilder>) { | |
self.headers.push((key.to_string(), value.to_string())); | |
self | |
} | |
(fn basic_auth( | |
mut self: Box<Self>, | |
username: &str, | |
password: Option<&str>, | |
) -> Box<dyn RequestBuilder>) { | |
self.auth = Some((username.to_string(), password.map(String::from))); | |
self | |
} | |
(fn bearer_auth(mut self: Box<Self>, token: &str) -> Box<dyn RequestBuilder>) { | |
self.auth = Some((token.to_string(), None)); | |
self | |
} | |
(fn send(self: Box<Self>) -> BoxFuture<'static, Result<Box<dyn Response>, Box<dyn std::error::Error>>>) { | |
let method = self.method; | |
let url = self.url.clone(); | |
let headers = self.headers.clone(); | |
let body = self.body.clone(); | |
let form = self.form.clone(); | |
let auth = self.auth.clone(); | |
Box::pin(async move { | |
let mut request = self.client.request(match method { | |
Method::GET => reqwest::Method::GET, | |
Method::POST => reqwest::Method::POST, | |
Method::PUT => reqwest::Method::PUT, | |
Method::DELETE => reqwest::Method::DELETE, | |
Method::HEAD => reqwest::Method::HEAD, | |
}, &url); | |
for (key, value) in headers { | |
request = request.header(&key, &value); | |
} | |
if let Some(body) = body { | |
request = request.body(body); | |
} | |
if let Some(form) = form { | |
request = request.body(form); | |
} | |
if let Some((username, password)) = auth { | |
match password { | |
Some(password) => { | |
request = request.basic_auth(username, Some(password)); | |
} | |
None => { | |
request = request.bearer_auth(&username); | |
} | |
} | |
} | |
let response = request.send().await?; | |
Ok(Box::new(ResponseImpl::new(response)) as Box<dyn Response>) | |
}) | |
} | |
} | |
} | |
#[cfg(feature = "impl")] | |
struct ResponseImpl { | |
response: reqwest::Response, | |
} | |
#[cfg(feature = "impl")] | |
impl ResponseImpl { | |
fn new(response: reqwest::Response) -> Self { | |
Self { response } | |
} | |
} | |
declare_trait_and_impl! { | |
impl Response for ResponseImpl { | |
(fn status(&self) -> StatusCode) { | |
StatusCode(self.response.status().as_u16()) | |
} | |
(fn headers_only_string_safe(&self) -> HashMap<String, String>) { | |
let mut headers = HashMap::new(); | |
for (key, value) in self.response.headers() { | |
if let Ok(v) = value.to_str() { | |
headers.insert(key.to_string(), v.to_string()); | |
} | |
} | |
headers | |
} | |
(fn bytes(self: Box<Self>) -> BoxFuture<'static, Result<Bytes, Error>>) { | |
let response = self.response; | |
Box::pin(async move { | |
response.bytes().await.map_err(|e| Error::Any(e.to_string())) | |
}) | |
} | |
(fn bytes_stream(self: Box<Self>) -> BoxStream<'static, Result<Bytes, Error>>) { | |
use futures_util::StreamExt; | |
Box::pin(self.response | |
.bytes_stream() | |
.map(|r| r.map_err(|e| Error::Any(e.to_string())))) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment