init: desert-framework workspace
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"desert-framework",
|
||||
"desert-framework-macros",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
desert-framework = { path = "desert-framework" }
|
||||
desert-framework-macros = { path = "desert-framework-macros" }
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "desert-framework-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2", features = ["full"] }
|
||||
quote = "1"
|
||||
proc-macro2 = "1"
|
||||
@@ -0,0 +1,218 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input, FnArg, ImplItemFn, ItemStruct, Meta, Pat, PatType, Token, Type,
|
||||
};
|
||||
|
||||
fn parse_controller_path(attr: TokenStream) -> String {
|
||||
if attr.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let meta: Meta = syn::parse(attr).expect("expected `path = \"...\"`");
|
||||
match meta {
|
||||
Meta::NameValue(nv) if nv.path.is_ident("path") => match &nv.value {
|
||||
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
|
||||
syn::Lit::Str(s) => s.value(),
|
||||
_ => panic!("expected string literal for path"),
|
||||
},
|
||||
_ => panic!("expected string literal for path"),
|
||||
},
|
||||
_ => panic!("expected `path = \"...\"`"),
|
||||
}
|
||||
}
|
||||
|
||||
fn route_path_from_attr(attr: TokenStream) -> String {
|
||||
let s: syn::LitStr = syn::parse(attr).expect("expected path string like `\"/path\"`");
|
||||
s.value()
|
||||
}
|
||||
|
||||
fn method_code(http: &str) -> u8 {
|
||||
match http {
|
||||
"get" => 0,
|
||||
"post" => 1,
|
||||
"put" => 2,
|
||||
"delete" => 3,
|
||||
"patch" => 4,
|
||||
_ => 255,
|
||||
}
|
||||
}
|
||||
|
||||
/// #[controller(path = "/api")] on struct
|
||||
#[proc_macro_attribute]
|
||||
pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let path = parse_controller_path(attr);
|
||||
let s = parse_macro_input!(item as ItemStruct);
|
||||
let name = &s.ident;
|
||||
|
||||
quote! {
|
||||
#s
|
||||
impl #name { pub const __CONTROLLER_PATH: &str = #path; }
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Generate cleaned method + metadata consts + handler factory
|
||||
fn process_route_method(http: &str, attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let method = parse_macro_input!(item as ImplItemFn);
|
||||
let name = &method.sig.ident;
|
||||
let is_async = method.sig.asyncness.is_some();
|
||||
let code = method_code(http);
|
||||
let path = route_path_from_attr(attr);
|
||||
|
||||
let extra: Vec<&FnArg> = method
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.filter(|a| !matches!(a, FnArg::Receiver(_)))
|
||||
.collect();
|
||||
|
||||
let pats: Vec<&Pat> = extra
|
||||
.iter()
|
||||
.map(|a| match a {
|
||||
FnArg::Typed(PatType { pat, .. }) => pat.as_ref(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let tys: Vec<&Type> = extra
|
||||
.iter()
|
||||
.map(|a| match a {
|
||||
FnArg::Typed(PatType { ty, .. }) => ty.as_ref(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let router_fn = code_to_ident(code);
|
||||
|
||||
let closure = if extra.is_empty() {
|
||||
if is_async {
|
||||
quote! { move || async move { state.#name().await } }
|
||||
} else {
|
||||
quote! { move || { state.#name() } }
|
||||
}
|
||||
} else if is_async {
|
||||
quote! {
|
||||
move |#(#pats: #tys),*| async move {
|
||||
state.#name(#(#pats),*).await
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
move |#(#pats: #tys),*| {
|
||||
state.#name(#(#pats),*)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let factory_name = syn::Ident::new(&format!("__make_route_{}", name), name.span());
|
||||
let method_const = syn::Ident::new(&format!("__ROUTE_METHOD_{}", name), name.span());
|
||||
let path_const = syn::Ident::new(&format!("__ROUTE_PATH_{}", name), name.span());
|
||||
|
||||
quote! {
|
||||
#method
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const #method_const: u8 = #code;
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const #path_const: &str = #path;
|
||||
|
||||
pub fn #factory_name(state: std::sync::Arc<Self>) -> ::axum::routing::MethodRouter<()> {
|
||||
#router_fn(#closure)
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn code_to_ident(code: u8) -> TokenStream2 {
|
||||
match code {
|
||||
0 => quote! { ::axum::routing::get },
|
||||
1 => quote! { ::axum::routing::post },
|
||||
2 => quote! { ::axum::routing::put },
|
||||
3 => quote! { ::axum::routing::delete },
|
||||
4 => quote! { ::axum::routing::patch },
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
process_route_method("get", attr, item)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
process_route_method("post", attr, item)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
process_route_method("put", attr, item)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
process_route_method("delete", attr, item)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
process_route_method("patch", attr, item)
|
||||
}
|
||||
|
||||
struct ImplRoutesInput {
|
||||
type_: syn::Path,
|
||||
methods: Vec<syn::Ident>,
|
||||
}
|
||||
|
||||
impl Parse for ImplRoutesInput {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let type_: syn::Path = input.parse()?;
|
||||
let _: Option<Token![,]> = input.parse()?;
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
let methods = content.parse_terminated(syn::Ident::parse, Token![,])?;
|
||||
Ok(ImplRoutesInput {
|
||||
type_,
|
||||
methods: methods.into_iter().collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// impl_routes!(MyCtrl, [hello, login])
|
||||
#[proc_macro]
|
||||
pub fn impl_routes(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as ImplRoutesInput);
|
||||
let ty = &input.type_;
|
||||
let methods = &input.methods;
|
||||
|
||||
let entries: Vec<TokenStream2> = methods
|
||||
.iter()
|
||||
.map(|m| {
|
||||
let factory = syn::Ident::new(&format!("__make_route_{}", m), m.span());
|
||||
let path_const = syn::Ident::new(&format!("__ROUTE_PATH_{}", m), m.span());
|
||||
|
||||
quote! {
|
||||
{
|
||||
let __path_suffix = <#ty>::#path_const;
|
||||
let __full_path = ::std::format!("{}{}", <#ty>::__CONTROLLER_PATH, __path_suffix);
|
||||
let __mr = <#ty>::#factory(state.clone());
|
||||
router = router.route(&__full_path, __mr);
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
impl #ty {
|
||||
pub fn get_router(self) -> ::axum::Router {
|
||||
let state = ::std::sync::Arc::new(self);
|
||||
let mut router = ::axum::Router::new();
|
||||
#(#entries)*
|
||||
router
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
/target
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "desert_framework"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.47.1", features = ["sync"] }
|
||||
log = "0.4.28"
|
||||
extended_rust = { git = "https://github.com/AlexIndustrial/extended_rust.git" }
|
||||
axum = "0.8"
|
||||
desert-framework-macros = { path = "../desert-framework-macros" }
|
||||
@@ -0,0 +1,4 @@
|
||||
pub trait Controller {
|
||||
type State: Send + Sync + 'static;
|
||||
fn register_routes(self) -> axum::Router<Self::State>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use std::any::TypeId;
|
||||
|
||||
use crate::service::Service;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dependency {
|
||||
pub type_id: TypeId,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub fn dep<T: ?Sized + 'static + Service>() -> Dependency {
|
||||
Dependency {
|
||||
type_id: TypeId::of::<T>(),
|
||||
name: T::name(),
|
||||
}
|
||||
}
|
||||
|
||||
pub type Deps = Vec<Dependency>;
|
||||
@@ -0,0 +1,8 @@
|
||||
pub mod controller;
|
||||
pub mod dependency;
|
||||
pub mod macros;
|
||||
pub mod manager;
|
||||
pub mod service;
|
||||
pub mod test;
|
||||
|
||||
pub use desert_framework_macros::*;
|
||||
@@ -0,0 +1,8 @@
|
||||
#[macro_export]
|
||||
macro_rules! inject_services {
|
||||
($manager:expr, $fn_name:expr, { $($var_name:ident : $service_type:ty),* $(,)? }) => {
|
||||
$(
|
||||
let $var_name = $manager.get::<$service_type>($fn_name).await.unwrap();
|
||||
)*
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::service::Service;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DependencyManager {
|
||||
dependencies: Arc<RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>>,
|
||||
}
|
||||
|
||||
impl DependencyManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
dependencies: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register<T: Send + Sync + 'static + Service>(&self) -> Arc<T> {
|
||||
let deps = T::deps();
|
||||
|
||||
let mut err_count = 0;
|
||||
|
||||
log::info!("Checking deps {}", T::name());
|
||||
for dep in deps {
|
||||
if self.check_by_type_id(dep.type_id).await {
|
||||
log::info!("> {} ok", dep.name)
|
||||
} else {
|
||||
err_count += 1;
|
||||
log::error!("> {} error", dep.name)
|
||||
}
|
||||
}
|
||||
|
||||
if err_count > 0 {
|
||||
log::error!("failed to create service");
|
||||
panic!("failed to create service {}", T::name())
|
||||
} else {
|
||||
log::info!("deps-check successfull");
|
||||
}
|
||||
|
||||
let child_manager = Arc::new(Self {
|
||||
dependencies: self.dependencies.clone(),
|
||||
});
|
||||
|
||||
let service_instance = Arc::new(T::new(child_manager).await);
|
||||
|
||||
let mut services = self.dependencies.write().await;
|
||||
let type_id = TypeId::of::<T>();
|
||||
services.insert(
|
||||
type_id,
|
||||
service_instance.clone() as Arc<dyn Any + Send + Sync>,
|
||||
);
|
||||
|
||||
return service_instance;
|
||||
}
|
||||
|
||||
async fn check_by_type_id(&self, type_id: TypeId) -> bool {
|
||||
let deps = self.dependencies.read().await;
|
||||
let got = deps.get(&type_id);
|
||||
return got.is_some();
|
||||
}
|
||||
|
||||
pub async fn get<T: Send + Sync + 'static + Service>(&self, from: &str) -> Option<Arc<T>> {
|
||||
let deps = self.dependencies.read().await;
|
||||
let type_id = TypeId::of::<T>();
|
||||
|
||||
let result = deps.get(&type_id).and_then(|arc_any| {
|
||||
let cloned = arc_any.clone();
|
||||
cloned.downcast::<T>().ok()
|
||||
});
|
||||
|
||||
if result.is_none() {
|
||||
log::warn!("{} unable to get {} now", from, T::name())
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use std::{sync::Arc, vec};
|
||||
|
||||
use crate::{dependency::Deps, manager::DependencyManager};
|
||||
|
||||
pub trait Service {
|
||||
fn new(manager: Arc<DependencyManager>) -> impl std::future::Future<Output = Self> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn deps() -> Deps {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
fn name() -> String;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use extended_rust::string;
|
||||
|
||||
use crate::{
|
||||
dependency::{dep, Deps},
|
||||
manager::DependencyManager,
|
||||
service::Service,
|
||||
};
|
||||
use crate::{controller, get, impl_routes, post};
|
||||
|
||||
pub struct TestService1 {}
|
||||
|
||||
impl Service for TestService1 {
|
||||
async fn new(_manager: Arc<DependencyManager>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
string!("1")
|
||||
}
|
||||
}
|
||||
|
||||
impl TestService1 {
|
||||
pub async fn get_hello_from_1(&self) -> String {
|
||||
string!("hello from service 1")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestService2 {}
|
||||
|
||||
impl Service for TestService2 {
|
||||
fn deps() -> Deps {
|
||||
vec![dep::<TestService1>()]
|
||||
}
|
||||
|
||||
async fn new(manager: Arc<DependencyManager>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let service1 = manager.get::<TestService1>(&Self::name()).await.unwrap();
|
||||
|
||||
println!("{}", service1.get_hello_from_1().await);
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
string!("2")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test() {
|
||||
let manager = DependencyManager::new();
|
||||
|
||||
let _service1 = manager.register::<TestService1>().await;
|
||||
let _service2 = manager.register::<TestService2>().await;
|
||||
}
|
||||
|
||||
#[controller(path = "/api")]
|
||||
pub struct AppState {}
|
||||
|
||||
impl AppState {
|
||||
#[get("/hello")]
|
||||
pub async fn hello(&self) -> &'static str {
|
||||
"hello world"
|
||||
}
|
||||
|
||||
#[get("/items/{id}")]
|
||||
pub async fn get_item(
|
||||
&self,
|
||||
axum::extract::Path(id): axum::extract::Path<String>,
|
||||
) -> String {
|
||||
format!("item: {}", id)
|
||||
}
|
||||
|
||||
#[post("/items")]
|
||||
pub async fn add_item(
|
||||
&self,
|
||||
axum::Json(body): axum::Json<String>,
|
||||
) -> String {
|
||||
format!("added: {}", body)
|
||||
}
|
||||
}
|
||||
|
||||
impl_routes!(AppState, [hello, get_item, add_item]);
|
||||
|
||||
pub async fn test_controller() {
|
||||
let controller = AppState {};
|
||||
|
||||
let _router: axum::Router = controller.get_router();
|
||||
}
|
||||
|
||||
pub async fn test_merge_controllers() {
|
||||
let c1 = AppState {};
|
||||
let router = axum::Router::new()
|
||||
.merge(c1.get_router());
|
||||
|
||||
let _: axum::Router = router;
|
||||
}
|
||||
Reference in New Issue
Block a user