use nidrs_extern::axum::extract::Request;
use nidrs_extern::axum::response::IntoResponse;
use nidrs_extern::axum::routing::Route;
use nidrs_extern::router::MetaRouter;
use nidrs_extern::router::StateCtx;
use nidrs_extern::tokio;
use nidrs_extern::tower;
use nidrs_extern::tower::Layer;
use nidrs_extern::{
axum::{self},
datasets::{self},
tokio::signal,
};
use std::convert::Infallible;
use std::{
any::Any,
collections::HashMap,
sync::{Arc, RwLock},
time::Duration,
};
use crate::{provider, shared::otr, template_format, AppResult, InnerMeta, Interceptor, Service};
static GLOBALS_KEY: &str = "Defaults";
pub trait Module {
fn init(self, ctx: ModuleCtx) -> ModuleCtx;
fn destroy(&self, ctx: &ModuleCtx);
}
pub struct DynamicModule<M>
where
M: Module,
{
pub module: Option<M>,
pub services: HashMap<String, Box<dyn Any>>,
pub exports: Vec<String>,
}
impl<M> DynamicModule<M>
where
M: Module,
{
pub fn new(module: M) -> Self {
DynamicModule { services: HashMap::new(), exports: Vec::new(), module: Some(module) }
}
pub fn provider(mut self, service: (String, Box<dyn Any>)) -> Self {
self.services.insert(service.0, service.1);
self
}
pub fn service<T: Service + 'static>(mut self, service: T) -> Self {
let (name, service) = provider(service);
self.services.insert(name, service);
self
}
pub fn export<T: Service + 'static>(mut self, service: T) -> Self {
let (name, service) = provider(service);
self.services.insert(name.clone(), service);
self.exports.push(name);
self
}
pub fn export2<T: Service + 'static, N: Into<String>>(mut self, service: T, name: Option<N>) -> Self {
let (raw_name, service) = provider(service);
let name = name.map(|n| n.into()).unwrap_or(raw_name);
self.services.insert(name.clone(), service);
self.exports.push(name);
self
}
}
impl<M> Module for DynamicModule<M>
where
M: Module,
{
fn init(self, ctx: ModuleCtx) -> ModuleCtx {
ctx
}
fn destroy(&self, ctx: &ModuleCtx) {}
}
#[derive(Debug, Clone)]
pub struct ModuleDefaults {
pub default_version: &'static str,
pub default_prefix: &'static str,
}
pub struct NidrsFactory<T: Module> {
pub module: Option<T>,
pub module_ctx: ModuleCtx,
pub router: axum::Router<StateCtx>,
pub port: u32,
pub rt: RwLock<Option<tokio::runtime::Runtime>>,
pub inter_apply: Vec<Box<dyn FnOnce(axum::Router<StateCtx>) -> axum::Router<StateCtx> + 'static>>,
pub router_hook: Box<dyn Fn(MetaRouter) -> axum::Router<StateCtx>>,
}
impl<T: Module> NidrsFactory<T> {
pub fn create(module: T) -> Self {
let router: axum::Router<StateCtx> = axum::Router::new();
let module_ctx = ModuleCtx::new(ModuleDefaults { default_version: "v1", default_prefix: "" });
NidrsFactory {
rt: RwLock::new(None),
router,
module: Some(module),
module_ctx,
port: 3000,
router_hook: Box::new(|r| r.router),
inter_apply: vec![],
}
}
pub fn default_prefix(mut self, prefix: &'static str) -> Self {
self.module_ctx.defaults.default_prefix = prefix;
self
}
pub fn default_version(mut self, v: &'static str) -> Self {
self.module_ctx.defaults.default_version = v;
self
}
pub fn default_uses<I: Interceptor + 'static + Sync + Send>(mut self, inter: I) -> Self {
let service_name = inter.__meta().get_data::<datasets::ServiceName>().unwrap().value().clone();
let interceptor = Arc::new(inter);
self.module_ctx.register_interceptor(GLOBALS_KEY, &service_name, Box::new(interceptor.clone()));
self.inter_apply.push(Box::new(move |router| {
router.layer(axum::middleware::from_fn({
move |req: axum::extract::Request, next: axum::middleware::Next| {
let inter = std::sync::Arc::clone(&interceptor);
async move {
let res = inter.intercept(req, next).await;
match res {
Ok(res) => Ok(res.into_response()),
Err(err) => Err(err),
}
}
}
}))
}));
self
}
pub fn default_layer<L>(mut self, middle: L) -> Self
where
L: Layer<Route> + Clone + Send + 'static,
L::Service: tower::Service<Request> + Clone + Send + 'static,
<L::Service as tower::Service<Request>>::Response: IntoResponse + 'static,
<L::Service as tower::Service<Request>>::Error: Into<Infallible> + 'static,
<L::Service as tower::Service<Request>>::Future: Send + 'static,
{
self.inter_apply.push(Box::new(move |router| router.layer::<L>(middle)));
self
}
pub fn each_router(mut self, hook: impl Fn(MetaRouter) -> axum::Router<StateCtx> + 'static) -> Self {
self.router_hook = Box::new(hook);
self
}
pub fn listen(mut self, port: u32) -> Self {
self.port = port;
let module = self.module.take().unwrap();
self.module_ctx = module.init(self.module_ctx);
let mut sub_router = axum::Router::new();
for router in self.module_ctx.routers.iter() {
sub_router = sub_router.merge((self.router_hook)(router.clone()));
}
self.router = self.router.merge(sub_router);
#[cfg(feature = "openapi")]
{
self.router = self.router.merge(nidrs_openapi::register(&self.module_ctx.routers));
nidrs_macro::log!("Swagger UI on {}", format!("http://127.0.0.1:{}/swagger-ui", self.port));
nidrs_macro::log!("Rapidoc UI on {}", format!("http://127.0.0.1:{}/rapidoc", self.port));
nidrs_macro::log!("Redoc UI on {}", format!("http://127.0.0.1:{}/redoc", self.port));
}
while let Some(apply) = self.inter_apply.pop() {
self.router = apply(self.router);
}
self
}
pub fn block(mut self) {
let server = || async {
let tcp = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", self.port)).await?;
let addr = tcp.local_addr()?;
nidrs_macro::log!("Listening on {}", addr);
axum::serve(tcp, self.router.with_state(StateCtx {})).await?;
AppResult::Ok(())
};
self.rt = RwLock::new(Some(
tokio::runtime::Builder::new_multi_thread()
.enable_all() .build()
.unwrap(),
));
if let Some(rt) = &*self.rt.write().unwrap() {
rt.block_on(async {
tokio::select! {
_ = server() => {
nidrs_macro::elog!("Server exited unexpectedly.");
},
_ = signal::ctrl_c() => {
nidrs_macro::log!("Received Ctrl+C, shutting down...");
}
}
});
}
self.module_ctx.destroy();
nidrs_macro::log!("Process is exiting now.");
let rt = self.rt.write().unwrap().take();
if let Some(rt) = rt {
rt.shutdown_timeout(Duration::from_secs(1));
}
}
pub fn destroy(&self) {
self.module_ctx.destroy();
nidrs_macro::log!("Process is exiting now.");
let rt = self.rt.write().unwrap().take();
if let Some(rt) = rt {
rt.shutdown_timeout(Duration::from_secs(1));
}
}
}
pub struct ModuleCtx {
pub defaults: ModuleDefaults,
pub modules: HashMap<String, Box<dyn Module>>,
pub services: HashMap<String, Box<dyn Any>>,
pub controllers: HashMap<String, Box<dyn Any>>,
pub routers: Vec<MetaRouter>,
pub interceptors: HashMap<String, Box<dyn Any>>,
pub imports: HashMap<String, Vec<String>>,
pub exports: HashMap<String, Vec<String>>,
pub deps: HashMap<String, Vec<String>>,
pub globals: HashMap<String, String>,
}
impl ModuleCtx {
pub fn new(defaults: ModuleDefaults) -> Self {
ModuleCtx {
defaults,
modules: HashMap::new(), services: HashMap::new(), controllers: HashMap::new(), routers: Vec::new(), interceptors: HashMap::new(), imports: HashMap::new(), exports: HashMap::new(), deps: HashMap::new(), globals: HashMap::new(), }
}
pub fn destroy(&self) {
for (_, module) in self.modules.iter() {
module.destroy(self);
}
}
pub fn get_controller<R: 'static>(&self, current_module_name: &str, service_name: &str) -> Arc<R> {
let svc_mods = self.deps.get(service_name).unwrap_or_else(|| panic!("[nidrs] not deps {} {}", current_module_name, service_name)); let imp_mods =
self.imports.get(current_module_name).unwrap_or_else(|| panic!("[nidrs] not import {}::{}", current_module_name, service_name)); let intersection_mods = svc_mods.iter().filter(|&m| imp_mods.contains(m)).cloned().collect::<Vec<_>>();
let first_mod = intersection_mods.first().unwrap_or(¤t_module_name.to_string()).clone();
let svc_key = format!("{}::{}", first_mod, service_name);
let svc = self.controllers.get(&svc_key).unwrap();
let svc = svc.downcast_ref::<std::sync::Arc<R>>().unwrap();
svc.clone()
}
pub fn register_controller(&mut self, current_module_name: &str, service_name: &str, controller: Box<dyn Any>) -> bool {
let svc_key = current_module_name.to_string() + "::" + service_name;
if !self.controllers.contains_key(svc_key.as_str()) {
self.controllers.insert(svc_key.clone(), controller);
self.deps.entry(service_name.to_string()).or_default().push(current_module_name.to_string());
nidrs_macro::log!("Registering controller {}.", svc_key);
return true;
}
false
}
pub fn get_interceptor<R: 'static>(&self, current_module_name: &str, service_name: &str) -> Arc<R> {
let current_module_name = GLOBALS_KEY;
let svc_key = format!("{}::{}", current_module_name, service_name);
let svc =
self.interceptors.get(&svc_key).unwrap_or_else(|| panic!("[nidrs] not inject {}::{} {}", current_module_name, service_name, svc_key));
let svc = svc.downcast_ref::<std::sync::Arc<R>>().unwrap();
svc.clone()
}
pub fn register_interceptor(&mut self, current_module_name: &str, service_name: &str, interceptor: Box<dyn Any>) -> bool {
let current_module_name = GLOBALS_KEY;
let svc_key = current_module_name.to_string() + "::" + service_name;
if !self.interceptors.contains_key(svc_key.as_str()) {
self.interceptors.insert(svc_key.clone(), interceptor);
self.deps.entry(service_name.to_string()).or_default().push(current_module_name.to_string());
nidrs_macro::log!("Registering interceptor {}.", svc_key);
return true;
}
false
}
pub fn get_service<R: 'static>(&self, current_module_name: &str, service_name: &str) -> Arc<R> {
let svc_mods = self.deps.get(service_name).unwrap_or_else(|| panic!("[nidrs] not deps {} {}", current_module_name, service_name)); let imp_mods =
self.imports.get(current_module_name).unwrap_or_else(|| panic!("[nidrs] not import {}::{}", current_module_name, service_name)); let intersection_mods = svc_mods.iter().filter(|&m| imp_mods.contains(m)).cloned().collect::<Vec<_>>();
let first_mod = intersection_mods.first().unwrap_or(¤t_module_name.to_string()).clone();
if first_mod != current_module_name && !self.exports.get(&first_mod).unwrap().contains(&service_name.to_string()) {
nidrs_macro::elog!("[{}] {} is not exported by {}", current_module_name, service_name, first_mod);
}
let svc_key = format!("{}::{}", first_mod, service_name);
let svc_key = if self.services.contains_key(&svc_key) {
svc_key
} else {
let mod_name = self
.globals
.get(service_name)
.unwrap_or_else(|| panic!("[nidrs] {}::{} inject {} error", current_module_name, service_name, svc_key))
.to_string();
format!("{}::{}", mod_name, service_name)
};
let svc = self.services.get(&svc_key).unwrap_or_else(|| panic!("[nidrs] {}::{} inject {} error", current_module_name, service_name, svc_key));
let svc =
svc.downcast_ref::<std::sync::Arc<R>>().unwrap_or_else(|| panic!("[nidrs] not downcast_ref {} {}", current_module_name, service_name));
svc.clone()
}
pub fn register_service<T: Into<String>>(&mut self, current_module_name: &str, service_name: T, service: Box<dyn Any>) -> bool {
let service_name = service_name.into();
let svc_key = current_module_name.to_string() + "::" + &service_name;
if !self.services.contains_key(svc_key.as_str()) {
self.services.insert(svc_key.clone(), service);
self.deps.entry(service_name.to_string()).or_default().push(current_module_name.to_string());
nidrs_macro::log!("Registering service {}.", svc_key);
return true;
} else {
nidrs_macro::elog!("Service {} already exists.", svc_key);
}
false
}
pub fn register_module(&mut self, current_module_name: &str, module: Box<dyn Module>) -> bool {
if !self.modules.contains_key(current_module_name) {
self.modules.insert(current_module_name.to_string(), module);
return true;
}
false
}
pub fn append_exports<T: Into<String>>(&mut self, current_module_name: &str, service_names: Vec<T>, is_global: bool) -> bool {
let mut success = true;
for service_name in service_names {
let service_name = service_name.into();
let svc_key = current_module_name.to_string() + "::" + &service_name;
if !self.exports.contains_key(current_module_name) {
self.exports.insert(current_module_name.to_string(), vec![service_name.to_string()]);
} else {
let exports = self.exports.get_mut(current_module_name).unwrap();
if !exports.contains(&service_name.to_string()) {
exports.push(service_name.to_string());
} else {
nidrs_macro::elog!("Service {} already exported.", svc_key);
success = false;
}
}
if is_global {
self.globals.insert(service_name.to_string(), current_module_name.to_string());
}
}
success
}
pub fn get_router_full(&self, meta: &InnerMeta) -> AppResult<String> {
let controller_path = otr(meta.get_data::<datasets::ControllerPath>(), "meta not nidrs::datasets::ControllerPath value")?.value();
let router_path = otr(meta.get_data::<datasets::RouterPath>(), "meta not nidrs::datasets::RouterPath value")?.value();
let version = *meta.get::<&str>("version").unwrap_or(&self.defaults.default_version);
let disable_default_prefix = meta.get_data::<datasets::DisableDefaultPrefix>().unwrap_or(&datasets::DisableDefaultPrefix(false)).value();
let full_path = if disable_default_prefix {
format!("{}{}", controller_path, router_path)
} else {
template_format(&format!("{}{}{}", self.defaults.default_prefix, controller_path, router_path), [("version", version)])
};
Ok(full_path)
}
}
#[cfg(test)]
mod tests {
use std::{sync::Arc, vec};
#[test]
fn test_nidrs_factory() {
use std::any::Any;
trait Controller: Any {
fn handle_request(&self);
fn as_any(&self) -> &dyn Any;
}
struct ConcreteService {
pub name: String,
};
impl Controller for ConcreteService {
fn handle_request(&self) {
println!("Handling request...");
}
fn as_any(&self) -> &dyn Any {
self
}
}
fn main() {
let service: Arc<dyn Controller> = Arc::new(ConcreteService { name: "hello".to_string() });
service.handle_request();
let service_ref: &dyn Controller = service.as_ref();
let service_any: &dyn Any = service_ref.as_any();
if let Some(concrete) = service_any.downcast_ref::<ConcreteService>() {
concrete.handle_request();
} else {
println!("Not a ConcreteService instance.");
}
let mut t = vec!["str", "st2"];
t.drain(..).for_each(|x| println!("{}", x));
}
main();
}
}