use super::AppContext;
use super::error_stack::ErrorHandler;
use super::route_methods;
use crate::internals::Router;
use crate::internals::service::AppService;
use crate::middlewares::Middleware;
pub use feather_runtime::Method;
use feather_runtime::runtime::server::Server;
pub use feather_runtime::runtime::server::ServerConfig;
use std::borrow::Cow;
use std::sync::Arc;
use std::{fmt::Display, net::ToSocketAddrs};
#[repr(C)]
pub struct Route {
pub method: Method,
pub path: Cow<'static, str>,
pub middleware: Arc<dyn Middleware>,
}
pub struct App {
routes: Vec<Route>,
middleware: Vec<Arc<dyn Middleware>>,
context: AppContext,
error_handler: Option<ErrorHandler>,
server_config: ServerConfig,
}
impl App {
#[must_use = "Does nothing if you don't use the `listen` method"]
pub fn new() -> Self {
#[cfg(feature = "log")]
#[cfg(debug_assertions)]
{
use std::sync::Once;
static INIT_LOGGER: Once = Once::new();
INIT_LOGGER.call_once(|| {
use tracing_subscriber::filter::filter_fn;
use tracing_subscriber::{Layer, prelude::*};
let layer = tracing_subscriber::fmt::layer()
.compact()
.with_target(false)
.with_file(false)
.with_line_number(false)
.with_target(false)
.with_thread_ids(false)
.with_level(true)
.with_filter(filter_fn(|meta| {
!meta.target().starts_with("feather_runtime") && !meta.target().starts_with("may")
}))
.boxed();
tracing_subscriber::registry().with(layer).init();
});
}
Self {
routes: Vec::new(),
middleware: Vec::new(),
context: AppContext::new(),
error_handler: None,
server_config: ServerConfig::default(),
}
}
pub fn without_logger() -> Self {
Self {
routes: Vec::new(),
middleware: Vec::new(),
context: AppContext::new(),
error_handler: None,
server_config: ServerConfig::default(),
}
}
pub fn with_config(config: ServerConfig) -> Self {
#[cfg(feature = "log")]
#[cfg(debug_assertions)]
{
use std::sync::Once;
static INIT_LOGGER: Once = Once::new();
INIT_LOGGER.call_once(|| {
use tracing_subscriber::filter::filter_fn;
use tracing_subscriber::{Layer, prelude::*};
let layer = tracing_subscriber::fmt::layer()
.compact()
.with_target(false)
.with_file(false)
.with_line_number(false)
.with_target(false)
.with_thread_ids(false)
.with_level(true)
.with_filter(filter_fn(|meta| {
!meta.target().starts_with("feather_runtime") && !meta.target().starts_with("may")
}))
.boxed();
tracing_subscriber::registry().with(layer).init();
});
}
Self {
routes: Vec::new(),
middleware: Vec::new(),
context: AppContext::new(),
error_handler: None,
server_config: config,
}
}
pub fn context(&mut self) -> &mut AppContext {
&mut self.context
}
#[inline]
pub fn set_error_handler(&mut self, handler: ErrorHandler) {
self.error_handler = Some(handler)
}
#[inline]
pub fn max_body(&mut self, size: usize) -> &mut Self {
self.server_config.max_body_size = size;
self
}
#[inline]
pub fn read_timeout(&mut self, seconds: u64) -> &mut Self {
self.server_config.read_timeout_secs = seconds;
self
}
#[inline]
pub fn workers(&mut self, count: usize) -> &mut Self {
self.server_config.workers = count;
self
}
#[inline]
pub fn stack_size(&mut self, size: usize) -> &mut Self {
self.server_config.stack_size = size;
self
}
#[inline]
pub fn route<M: Middleware + 'static>(&mut self, method: Method, path: impl Into<Cow<'static, str>>, middleware: M) {
self.routes.push(Route {
method,
path: path.into(),
middleware: Arc::new(middleware),
});
}
pub fn mount(&mut self, prefix: impl Into<String>, router: Router) {
let prefix = prefix.into();
let prefix_trimmed = prefix.trim_matches('/');
for mut route in router.routes {
let path_trimmed = route.path.trim_matches('/');
let new_path = if path_trimmed.is_empty() {
format!("/{}", prefix_trimmed)
} else {
format!("/{}/{}", prefix_trimmed, path_trimmed)
};
route.path = Cow::Owned(new_path);
if !router.middleware.is_empty() {
route.middleware = Arc::new(super::router::ScopedMiddleware {
router_stack: router.middleware.clone(),
route_handler: route.middleware,
});
}
self.routes.push(route);
}
}
#[inline]
pub fn use_middleware(&mut self, middleware: impl Middleware + 'static) {
self.middleware.push(Arc::new(middleware));
}
route_methods!(
GET get
POST post
PUT put
DELETE delete
PATCH patch
HEAD head
OPTIONS options
);
pub fn listen(self, address: impl ToSocketAddrs + Display) {
let svc = AppService {
routes: self.routes,
middleware: self.middleware,
context: self.context,
error_handler: self.error_handler,
};
println!("Feather listening on : http://{address}",);
Server::with_config(svc, self.server_config).run(address).expect("Failed to start server");
}
}