use crate::{
request::{ElifPath, ElifQuery, ElifRequest, ElifState},
response::{ElifJson, ElifResponse},
routing::{params::ParamType, HttpMethod},
};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{future::Future, pin::Pin, sync::Arc};
use crate::{response::ApiResponse, HttpResult};
use elif_core::container::IocContainer;
#[derive(Debug, Serialize, Deserialize)]
pub struct QueryParams {
pub page: Option<u32>,
pub per_page: Option<u32>,
pub sort: Option<String>,
pub order: Option<String>,
pub filter: Option<String>,
}
impl Default for QueryParams {
fn default() -> Self {
Self {
page: Some(1),
per_page: Some(20),
sort: Some("id".to_string()),
order: Some("asc".to_string()),
filter: None,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaginationMeta {
pub page: u32,
pub per_page: u32,
pub total: Option<u64>,
pub total_pages: Option<u32>,
pub has_more: bool,
}
#[derive(Clone)]
pub struct BaseController;
impl Default for BaseController {
fn default() -> Self {
Self::new()
}
}
impl BaseController {
pub fn new() -> Self {
Self
}
pub fn normalize_pagination(&self, params: &QueryParams) -> (u32, u32, u64) {
let page = params.page.unwrap_or(1).max(1);
let per_page = params.per_page.unwrap_or(20).min(100).max(1);
let offset = (page - 1) * per_page;
(page, per_page, offset as u64)
}
pub fn success_response<T: Serialize>(&self, data: T) -> HttpResult<ElifResponse> {
let api_response = ApiResponse::success(data);
ElifResponse::ok().json(&api_response)
}
pub fn created_response<T: Serialize>(&self, data: T) -> HttpResult<ElifResponse> {
let api_response = ApiResponse::success(data);
ElifResponse::created().json(&api_response)
}
pub fn paginated_response<T: Serialize>(
&self,
data: Vec<T>,
meta: PaginationMeta,
) -> HttpResult<ElifResponse> {
let response_data = serde_json::json!({
"data": data,
"meta": meta
});
ElifResponse::ok().json(&response_data)
}
pub fn deleted_response<T: Serialize>(
&self,
resource_name: &str,
deleted_id: Option<T>,
) -> HttpResult<ElifResponse> {
let mut response_data = serde_json::json!({
"message": format!("{} deleted successfully", resource_name)
});
if let Some(id) = deleted_id {
response_data["deleted_id"] = serde_json::to_value(id)?;
}
let api_response = ApiResponse::success(response_data);
ElifResponse::ok().json(&api_response)
}
}
pub trait Controller: Send + Sync {
fn index(
&self,
container: ElifState<Arc<IocContainer>>,
params: ElifQuery<QueryParams>,
) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
fn show(
&self,
container: ElifState<Arc<IocContainer>>,
id: ElifPath<String>,
) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
fn create(
&self,
container: ElifState<Arc<IocContainer>>,
data: ElifJson<Value>,
) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
fn update(
&self,
container: ElifState<Arc<IocContainer>>,
id: ElifPath<String>,
data: ElifJson<Value>,
) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
fn destroy(
&self,
container: ElifState<Arc<IocContainer>>,
id: ElifPath<String>,
) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
}
#[derive(Debug, Clone)]
pub struct RouteParam {
pub name: String,
pub param_type: ParamType,
pub required: bool,
pub default: Option<String>,
}
impl RouteParam {
pub fn new(name: &str, param_type: ParamType) -> Self {
Self {
name: name.to_string(),
param_type,
required: true,
default: None,
}
}
pub fn optional(mut self) -> Self {
self.required = false;
self
}
pub fn with_default(mut self, default: &str) -> Self {
self.default = Some(default.to_string());
self.required = false;
self
}
}
#[derive(Debug, Clone)]
pub struct ControllerRoute {
pub method: HttpMethod,
pub path: String,
pub handler_name: String,
pub middleware: Vec<String>,
pub params: Vec<RouteParam>,
}
impl ControllerRoute {
pub fn new(method: HttpMethod, path: &str, handler_name: &str) -> Self {
Self {
method,
path: path.to_string(),
handler_name: handler_name.to_string(),
middleware: vec![],
params: vec![],
}
}
pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
self.middleware = middleware;
self
}
pub fn with_params(mut self, params: Vec<RouteParam>) -> Self {
self.params = params;
self
}
pub fn add_param(mut self, param: RouteParam) -> Self {
self.params.push(param);
self
}
}
#[async_trait]
pub trait ElifController: Send + Sync + 'static {
fn name(&self) -> &str;
fn base_path(&self) -> &str;
fn routes(&self) -> Vec<ControllerRoute>;
fn dependencies(&self) -> Vec<String> {
vec![]
}
async fn handle_request(
self: Arc<Self>,
method_name: String,
request: ElifRequest,
) -> HttpResult<ElifResponse>;
async fn handle_request_dyn(
&self,
method_name: String,
request: ElifRequest,
) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().json(&serde_json::json!({
"controller": self.name(),
"method": method_name,
"message": "Dynamic dispatch called successfully",
"status": "Phase 3 implementation working",
"path": request.path(),
"http_method": format!("{:?}", request.method)
})).unwrap_or_else(|_| ElifResponse::ok().text("Controller method called")))
}
}
#[macro_export]
macro_rules! controller_dispatch {
($self:expr, $method_name:expr, $request:expr, {
$($method:literal => $handler:expr),*
}) => {
match $method_name.as_str() {
$($method => Box::pin($handler($self, $request)),)*
_ => Box::pin(async move {
use $crate::response::ElifResponse;
Ok(ElifResponse::not_found().text(&format!("Handler '{}' not found", $method_name)))
})
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn test_base_controller_creation() {
let _controller = BaseController::new();
}
#[tokio::test]
async fn test_pagination_normalization() {
let controller = BaseController::new();
let params = QueryParams {
page: Some(5),
per_page: Some(10),
..Default::default()
};
let (page, per_page, offset) = controller.normalize_pagination(¶ms);
assert_eq!(page, 5);
assert_eq!(per_page, 10);
assert_eq!(offset, 40);
}
#[tokio::test]
async fn test_pagination_limits() {
let controller = BaseController::new();
let params = QueryParams {
page: Some(0),
per_page: Some(200),
..Default::default()
};
let (page, per_page, offset) = controller.normalize_pagination(¶ms);
assert_eq!(page, 1);
assert_eq!(per_page, 100);
assert_eq!(offset, 0);
}
#[tokio::test]
async fn test_success_response_creation() {
let controller = BaseController::new();
let data = json!({"message": "test"});
let response = controller.success_response(data);
assert!(response.is_ok());
}
#[tokio::test]
async fn test_pagination_meta_creation() {
let meta = PaginationMeta {
page: 1,
per_page: 20,
total: Some(100),
total_pages: Some(5),
has_more: true,
};
assert_eq!(meta.page, 1);
assert_eq!(meta.per_page, 20);
assert_eq!(meta.total, Some(100));
}
}