#![forbid(unsafe_code)]
extern crate self as caravan_rpc;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::{Arc, OnceLock, RwLock};
pub use caravan_rpc_macros::wagon;
pub mod codec;
pub mod errors;
pub mod peers;
pub mod resources;
#[cfg(feature = "client")]
pub mod dispatch;
#[cfg(feature = "server")]
pub mod server;
pub use errors::{RpcError, RpcRemoteError, RpcTransportError};
pub use peers::{PeerEntry, peer_for};
#[cfg(feature = "resources-rabbit")]
pub use resources::RabbitMQQueue;
#[cfg(feature = "resources-redis")]
pub use resources::RedisStreamQueue;
pub use resources::{
BlobError, BlobStore, LocalFsBlobStore, MessageQueue, QueueError, auto_register_resources,
};
#[cfg(feature = "resources-aws")]
pub use resources::{S3BlobStore, SqsQueue};
#[doc(hidden)]
pub mod __macro_support {
pub use async_trait;
#[cfg(feature = "server")]
pub use axum;
pub use inventory;
pub use serde_json;
}
pub struct HttpAdapterFactory {
pub interface_name: &'static str,
pub type_id_fn: fn() -> std::any::TypeId,
pub construct: fn(peer: PeerEntry) -> Box<dyn Any + Send + Sync>,
}
inventory::collect!(HttpAdapterFactory);
fn lookup_http_factory<T: ?Sized + 'static>() -> Option<&'static HttpAdapterFactory> {
let want = TypeId::of::<T>();
inventory::iter::<HttpAdapterFactory>
.into_iter()
.find(|f| (f.type_id_fn)() == want)
}
#[cfg(feature = "server")]
pub struct HttpServerFactory {
pub interface_name: &'static str,
pub build_router_from_registry:
fn() -> Result<crate::__macro_support::axum::Router, &'static str>,
}
#[cfg(feature = "server")]
inventory::collect!(HttpServerFactory);
#[cfg(feature = "server")]
pub async fn run_or_serve<F, Fut>(user_main: F) -> Result<(), RpcError>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<(), RpcError>>,
{
let role = std::env::var("CARAVAN_RPC_ROLE").unwrap_or_default();
if let Some(iface_name) = role.strip_prefix("peer-") {
let factory = inventory::iter::<HttpServerFactory>
.into_iter()
.find(|f| f.interface_name == iface_name)
.unwrap_or_else(|| {
panic!(
"caravan-rpc: CARAVAN_RPC_ROLE={role:?} but no HttpServerFactory \
registered for interface {iface_name:?}. Did you mark the trait \
with #[wagon] and have your impl crate compiled into this binary?"
)
});
let router = (factory.build_router_from_registry)().unwrap_or_else(|msg| {
panic!("caravan-rpc: peer {iface_name} failed to build router: {msg}")
});
if std::env::var("AWS_LAMBDA_RUNTIME_API").is_ok() {
eprintln!("caravan peer {iface_name} serving via Lambda runtime");
lambda_http::run(router)
.await
.expect("lambda_http::run returned error");
return Ok(());
}
let addr: std::net::SocketAddr = std::env::var("CARAVAN_RPC_BIND_ADDR")
.unwrap_or_else(|_| "0.0.0.0:8080".to_string())
.parse()
.expect("CARAVAN_RPC_BIND_ADDR must parse as SocketAddr");
eprintln!("caravan peer {iface_name} serving on {addr}");
server::serve_forever(addr, router)
.await
.expect("serve_forever returned error");
Ok(())
} else {
user_main().await
}
}
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
type Registry = RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>;
fn registry() -> &'static Registry {
static R: OnceLock<Registry> = OnceLock::new();
R.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn provide<T: ?Sized + Send + Sync + 'static>(impl_: Arc<T>) {
let mut g = registry().write().expect("caravan-rpc registry poisoned");
g.insert(
TypeId::of::<T>(),
Box::new(impl_) as Box<dyn Any + Send + Sync>,
);
}
pub fn client<T: ?Sized + Send + Sync + 'static>() -> Arc<T> {
try_client::<T>().unwrap_or_else(|| {
panic!(
"no impl registered for type {}; call provide::<{}>(Arc::new(impl)) at startup",
std::any::type_name::<T>(),
std::any::type_name::<T>()
)
})
}
pub fn try_client<T: ?Sized + Send + Sync + 'static>() -> Option<Arc<T>> {
if let Some(factory) = lookup_http_factory::<T>() {
let serving_self = std::env::var("CARAVAN_RPC_ROLE")
.ok()
.and_then(|role| {
role.strip_prefix("peer-")
.map(|iface| iface == factory.interface_name)
})
.unwrap_or(false);
if !serving_self {
match peer_for(factory.interface_name) {
Some(entry @ PeerEntry::Http { .. })
| Some(entry @ PeerEntry::Lambda { .. }) => {
let boxed = (factory.construct)(entry);
return Some(
*boxed.downcast::<Arc<T>>().expect(
"caravan-rpc: HttpAdapterFactory.construct returned wrong type",
),
);
}
_ => {}
}
}
}
let g = registry().read().expect("caravan-rpc registry poisoned");
g.get(&TypeId::of::<T>()).map(|entry| {
entry
.downcast_ref::<Arc<T>>()
.expect("caravan-rpc registry type mismatch (internal bug)")
.clone()
})
}
pub fn is_provided<T: ?Sized + Send + Sync + 'static>() -> bool {
let g = registry().read().expect("caravan-rpc registry poisoned");
g.contains_key(&TypeId::of::<T>())
}
#[doc(hidden)]
pub fn __clear_registry_for_tests() {
let mut g = registry().write().expect("caravan-rpc registry poisoned");
g.clear();
}