Skip to content

Instantly share code, notes, and snippets.

@shurizzle
Last active October 18, 2024 17:06
Show Gist options
  • Save shurizzle/1f676ede91630f37b0e4958a93e3700c to your computer and use it in GitHub Desktop.
Save shurizzle/1f676ede91630f37b0e4958a93e3700c to your computer and use it in GitHub Desktop.
Blocking and async
use std::fmt;
use url::Url;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub struct OpaqueError;
impl fmt::Display for OpaqueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "shit happens")
}
}
impl std::error::Error for OpaqueError {}
#[allow(async_fn_in_trait)]
pub trait AsyncHttpClient {
async fn get(&self, url: Url) -> Result<String, OpaqueError>;
}
pub trait BlockingHttpClient {
fn get(&self, url: Url) -> Result<String, OpaqueError>;
}
struct BlockingHttpBridge<T: BlockingHttpClient>(T);
impl<T: BlockingHttpClient> AsyncHttpClient for BlockingHttpBridge<T> {
#[inline(always)]
async fn get(&self, url: Url) -> Result<String, OpaqueError> {
self.0.get(url)
}
}
struct ApiClient<Http: AsyncHttpClient>(Http);
impl<Http: AsyncHttpClient> ApiClient<Http> {
pub fn new(client: Http) -> Self {
Self(client)
}
pub async fn get_ip(&self) -> Result<String, OpaqueError> {
self.0
.get(Url::parse("https://ipinfo.io/ip").map_err(|_| OpaqueError)?)
.await
}
}
mod internal {
use std::{
future::Future,
pin::Pin,
task::{Context, Poll::*, RawWaker, RawWakerVTable, Waker},
};
unsafe fn clone(_: *const ()) -> RawWaker {
RawWaker::new(core::ptr::null(), &WAKER_VTABLE)
}
unsafe fn wake(_: *const ()) {}
unsafe fn wake_by_ref(_: *const ()) {}
unsafe fn drop(_: *const ()) {}
const WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
const RAW_WAKER: RawWaker = RawWaker::new(core::ptr::null(), &WAKER_VTABLE);
static WAKER: Waker = unsafe { Waker::from_raw(RAW_WAKER) };
#[inline(always)]
pub fn block_on<O, F: Future<Output = O>>(mut f: F) -> O {
let mut pin = unsafe { Pin::new_unchecked(&mut f) };
let mut ctx = Context::from_waker(&WAKER);
loop {
match F::poll(pin.as_mut(), &mut ctx) {
Ready(res) => return res,
Pending => (),
}
}
}
}
use internal::block_on;
pub struct AsyncClient<Http: AsyncHttpClient>(ApiClient<Http>);
pub struct BlockingClient<Http: BlockingHttpClient>(ApiClient<BlockingHttpBridge<Http>>);
impl<Http: AsyncHttpClient> AsyncClient<Http> {
#[inline(always)]
pub fn new(http: Http) -> Self {
Self(ApiClient::new(http))
}
#[inline(always)]
pub async fn get_ip(&self) -> Result<String, OpaqueError> {
self.0.get_ip().await
}
}
impl<Http: AsyncHttpClient + Default> Default for AsyncClient<Http> {
#[inline(always)]
fn default() -> Self {
Self::new(Http::default())
}
}
impl<Http: BlockingHttpClient> BlockingClient<Http> {
#[inline(always)]
pub fn new(http: Http) -> Self {
Self(ApiClient::new(BlockingHttpBridge(http)))
}
#[inline(never)]
pub fn get_ip(&self) -> Result<String, OpaqueError> {
block_on(self.0.get_ip())
}
}
impl<Http: BlockingHttpClient + Default> Default for BlockingClient<Http> {
#[inline(always)]
fn default() -> Self {
Self::new(Http::default())
}
}
// impl HttpClient for reqwest::Client {
// async fn get(&self, url: Url) -> Result<String, OpaqueError> {
// let req = self.get(url).build().map_err(|_| OpaqueError)?;
// let res = self.execute(req).await.map_err(|_| OpaqueError)?;
// res.text().await.map_err(|_| OpaqueError)
// }
// }
impl BlockingHttpClient for ureq::Agent {
#[inline(never)]
fn get(&self, url: Url) -> Result<String, OpaqueError> {
self.request_url("GET", &url)
.call()
.map_err(|_| OpaqueError)?
.into_string()
.map_err(|_| OpaqueError)
}
}
// #[tokio::main]
// async fn main() {
// let client = AsyncClient::new();
// _ = dbg!(client.index().await);
// }
fn main() {
let client = BlockingClient::new(ureq::Agent::new());
_ = dbg!(client.get_ip());
}
[package]
name = "blocking-async"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "blocking_async"
path = "blocking_async.rs"
[profile.release]
panic = "abort"
[dependencies]
url = "2.5.2"
ureq = "2.10.0"
# reqwest = "0.12.5"
# tokio = { version = "1.38.1", features = ["full"] }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment