Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ axum-test = { version = "17.0.1", optional = true }
chrono = { workspace = true }
cfg-if = "1"

uuid = { version = "1.10.0", features = ["v4", "fast-rng"] }
uuid = { version = "1.10.0", features = ["v4", "fast-rng", "serde"] }

# File Upload
opendal = { version = "0.50.2", default-features = false, features = [
Expand Down Expand Up @@ -158,6 +158,8 @@ rusty-sidekiq = { version = "0.11.0", default-features = false, optional = true
bb8 = { version = "0.8.1", optional = true }

scraper = { version = "0.21.0", features = ["deterministic"], optional = true }
aide = { "version" = "0.14.0", "features" = ["axum", "axum-json"]}
schemars = { "version" = "0.8.21", "features" = ["uuid1"]}

[workspace.dependencies]
colored = { version = "2" }
Expand Down
51 changes: 35 additions & 16 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ cfg_if::cfg_if! {
}
use std::{net::SocketAddr, sync::Arc};

use aide::{axum::ApiRouter, openapi::OpenApi, transform::TransformOpenApi};
use async_trait::async_trait;
use axum::Router as AxumRouter;

use crate::{
bgworker::{self, Queue},
Expand Down Expand Up @@ -54,6 +54,12 @@ pub struct AppContext {
pub cache: Arc<cache::Cache>,
}

impl std::fmt::Debug for AppContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AppContext").finish()
}
}

/// A trait that defines hooks for customizing and extending the behavior of a
/// web server application.
///
Expand Down Expand Up @@ -110,24 +116,32 @@ pub trait Hooks: Send {
///
/// # Returns
/// A Result indicating success () or an error if the server fails to start.
async fn serve(app: AxumRouter, ctx: &AppContext, serve_params: &ServeParams) -> Result<()> {
async fn serve(app: ApiRouter, ctx: &AppContext, serve_params: &ServeParams) -> Result<()> {
aide::generate::on_error(|error| {
panic!("{error}");
});

let listener = tokio::net::TcpListener::bind(&format!(
"{}:{}",
serve_params.binding, serve_params.port
))
.await?;

let cloned_ctx = ctx.clone();
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(async move {
shutdown_signal().await;
tracing::info!("shutting down...");
Self::on_shutdown(&cloned_ctx).await;
})
.await?;

let mut api = OpenApi::default();
let app = app
.finish_api_with(&mut api, Self::api_docs)
.into_make_service_with_connect_info::<SocketAddr>();
std::fs::write("docs.json", serde_json::to_string_pretty(&api).unwrap()).unwrap();

axum::serve(listener, app)
.with_graceful_shutdown(async move {
shutdown_signal().await;
tracing::info!("shutting down...");
Self::on_shutdown(&cloned_ctx).await;
})
.await?;

Ok(())
}
Expand Down Expand Up @@ -157,8 +171,8 @@ pub trait Hooks: Send {
///
/// # Errors
/// Return an [`Result`] when the router could not be created
async fn before_routes(_ctx: &AppContext) -> Result<AxumRouter<AppContext>> {
Ok(AxumRouter::new())
async fn before_routes(_ctx: &AppContext) -> Result<ApiRouter<AppContext>> {
Ok(ApiRouter::new())
}

/// Invoke this function after the Loco routers have been constructed. This
Expand All @@ -167,7 +181,7 @@ pub trait Hooks: Send {
///
/// # Errors
/// Axum router error
async fn after_routes(router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
async fn after_routes(router: ApiRouter, _ctx: &AppContext) -> Result<ApiRouter> {
Ok(router)
}

Expand All @@ -194,6 +208,11 @@ pub trait Hooks: Send {
/// Defines the application's routing configuration.
fn routes(_ctx: &AppContext) -> AppRoutes;

fn api_docs(api: TransformOpenApi) -> TransformOpenApi {
api.title(Self::app_name())
.version(Self::app_version().as_str())
}

// Provides the options to change Loco [`AppContext`] after initialization.
async fn after_context(ctx: AppContext) -> Result<AppContext> {
Ok(ctx)
Expand Down Expand Up @@ -242,7 +261,7 @@ pub trait Initializer: Sync + Send {
/// Occurs after the app's `after_routes`.
/// Use this to compose additional functionality and wire it into an Axum
/// Router
async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
async fn after_routes(&self, router: ApiRouter, _ctx: &AppContext) -> Result<ApiRouter> {
Ok(router)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! your application.
use std::path::PathBuf;

use axum::Router;
use aide::axum::ApiRouter;
#[cfg(feature = "with-db")]
use sea_orm_migration::MigratorTrait;
use tokio::{select, signal, task::JoinHandle};
Expand Down Expand Up @@ -44,7 +44,7 @@ pub struct BootResult {
/// Application Context
pub app_context: AppContext,
/// Web server routes
pub router: Option<Router>,
pub router: Option<ApiRouter>,
/// worker processor
pub run_worker: bool,
}
Expand Down
14 changes: 7 additions & 7 deletions src/controller/app_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

use std::{fmt, sync::OnceLock};

use axum::Router as AXRouter;
use aide::axum::{routing::ApiMethodRouter, ApiRouter};

use regex::Regex;

use crate::{
Expand All @@ -26,11 +27,10 @@ pub struct AppRoutes {
routes: Vec<Routes>,
}

#[derive(Debug)]
pub struct ListRoutes {
pub uri: String,
pub actions: Vec<axum::http::Method>,
pub method: axum::routing::MethodRouter<AppContext>,
pub method: ApiMethodRouter<AppContext>,
}

impl fmt::Display for ListRoutes {
Expand Down Expand Up @@ -170,8 +170,8 @@ impl AppRoutes {
pub fn to_router<H: Hooks>(
&self,
ctx: AppContext,
mut app: AXRouter<AppContext>,
) -> Result<AXRouter> {
mut app: ApiRouter<AppContext>,
) -> Result<ApiRouter> {
// IMPORTANT: middleware ordering in this function is opposite to what you
// intuitively may think. when using `app.layer` to add individual middleware,
// the LAST middleware is the FIRST to meet the outside world (a user request
Expand All @@ -190,10 +190,10 @@ impl AppRoutes {
// issues in compile times itself (https://github.com/rust-lang/crates.io/pull/7443).
//
for router in self.collect() {
// panic!("Adding {} to router", router.uri);
tracing::info!("{}", router.to_string());
app = app.route(&router.uri, router.method);
app = app.api_route(&router.uri, router.method);
}

let middlewares = self.middlewares::<H>(&ctx);
for mid in middlewares {
app = mid.apply(app)?;
Expand Down
5 changes: 3 additions & 2 deletions src/controller/describe.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::sync::OnceLock;

use aide::axum::routing::ApiMethodRouter;
use axum::{http, routing::MethodRouter};
use regex::Regex;

Expand All @@ -16,8 +17,8 @@ fn get_describe_method_action() -> &'static Regex {
/// Currently axum not exposed the action type of the router. for hold extra
/// information about routers we need to convert the `method` to string and
/// capture the details
pub fn method_action(method: &MethodRouter<AppContext>) -> Vec<http::Method> {
let method_str = format!("{method:?}");
pub fn method_action(method: &ApiMethodRouter<AppContext>) -> Vec<http::Method> {
let method_str = format!("{:?}", MethodRouter::<AppContext>::from(method.clone()));

get_describe_method_action()
.captures(&method_str)
Expand Down
6 changes: 2 additions & 4 deletions src/controller/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use axum::{
body::Body,
http::{response::Builder, HeaderName, HeaderValue},
response::{Html, IntoResponse, Redirect, Response},
Json,
};
use axum_extra::extract::cookie::Cookie;
use bytes::{BufMut, BytesMut};
Expand All @@ -34,10 +35,7 @@ use serde::Serialize;
use serde_json::json;

use crate::{
controller::{
views::{self, ViewRenderer},
Json,
},
controller::views::{self, ViewRenderer},
Result,
};

Expand Down
14 changes: 8 additions & 6 deletions src/controller/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
//! reporting. These routes are commonly used to monitor the health of the
//! application and its dependencies.

use axum::{extract::State, response::Response, routing::get};
use aide::axum::routing::get;
use axum::{extract::State, Json};
use schemars::JsonSchema;
use serde::Serialize;

use super::{format, routes::Routes};
use crate::{app::AppContext, Result};
use super::routes::Routes;
use crate::app::AppContext;

/// Represents the health status of the application.
#[derive(Serialize)]
#[derive(Serialize, JsonSchema)]
struct Health {
pub ok: bool,
}

/// Check the healthiness of the application bt ping to the redis and the DB to
/// insure that connection
async fn health(State(ctx): State<AppContext>) -> Result<Response> {
async fn health(State(ctx): State<AppContext>) -> Json<Health> {
let mut is_ok = match ctx.db.ping().await {
Ok(()) => true,
Err(error) => {
Expand All @@ -31,7 +33,7 @@ async fn health(State(ctx): State<AppContext>) -> Result<Response> {
is_ok = false;
}
}
format::json(Health { ok: is_ok })
Json(Health { ok: is_ok })
}

/// Defines and returns the health-related routes.
Expand Down
5 changes: 5 additions & 0 deletions src/controller/middleware/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
//! ```
use std::collections::HashMap;

use aide::OperationInput;
use axum::{
extract::{FromRef, FromRequestParts, Query},
http::{request::Parts, HeaderMap},
Expand Down Expand Up @@ -51,6 +52,8 @@ pub struct JWTWithUser<T: Authenticable> {
pub user: T,
}

impl<T: Authenticable> OperationInput for JWTWithUser<T> {}

// Implement the FromRequestParts trait for the Auth struct
impl<S, T> FromRequestParts<S> for JWTWithUser<T>
where
Expand Down Expand Up @@ -89,6 +92,8 @@ pub struct JWT {
pub claims: auth::jwt::UserClaims,
}

impl OperationInput for JWT {}

// Implement the FromRequestParts trait for the Auth struct
impl<S> FromRequestParts<S> for JWT
where
Expand Down
4 changes: 2 additions & 2 deletions src/controller/middleware/catch_panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! internal server error response. This middleware helps ensure that the
//! application can gracefully handle unexpected errors without crashing the
//! server.
use axum::Router as AXRouter;
use aide::axum::ApiRouter;
use serde::{Deserialize, Serialize};
use tower_http::catch_panic::CatchPanicLayer;

Expand Down Expand Up @@ -53,7 +53,7 @@ impl MiddlewareLayer for CatchPanic {
}

/// Applies the Catch Panic middleware layer to the Axum router.
fn apply(&self, app: AXRouter<AppContext>) -> Result<AXRouter<AppContext>> {
fn apply(&self, app: ApiRouter<AppContext>) -> Result<ApiRouter<AppContext>> {
Ok(app.layer(CatchPanicLayer::custom(handle_panic)))
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/controller/middleware/compression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! times and reducing bandwidth usage. The middleware configuration allows for
//! enabling or disabling compression based on the application settings.

use axum::Router as AXRouter;
use aide::axum::ApiRouter;
use serde::{Deserialize, Serialize};
use tower_http::compression::CompressionLayer;

Expand Down Expand Up @@ -33,7 +33,7 @@ impl MiddlewareLayer for Compression {
}

/// Applies the Compression middleware layer to the Axum router.
fn apply(&self, app: AXRouter<AppContext>) -> Result<AXRouter<AppContext>> {
fn apply(&self, app: ApiRouter<AppContext>) -> Result<ApiRouter<AppContext>> {
Ok(app.layer(CompressionLayer::new()))
}
}
4 changes: 2 additions & 2 deletions src/controller/middleware/cors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use std::time::Duration;

use axum::Router as AXRouter;
use aide::axum::ApiRouter;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tower_http::cors::{self, Any};
Expand Down Expand Up @@ -157,7 +157,7 @@ impl MiddlewareLayer for Cors {
}

/// Applies the CORS middleware layer to the Axum router.
fn apply(&self, app: AXRouter<AppContext>) -> Result<AXRouter<AppContext>> {
fn apply(&self, app: ApiRouter<AppContext>) -> Result<ApiRouter<AppContext>> {
Ok(app.layer(self.cors()?))
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/controller/middleware/etag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@

use std::task::{Context, Poll};

use axum::{
body::Body, extract::Request, http::StatusCode, response::Response, Router as AXRouter,
};
use aide::axum::ApiRouter;
use axum::{body::Body, extract::Request, http::StatusCode, response::Response};
use futures_util::future::BoxFuture;
use hyper::header::{ETAG, IF_NONE_MATCH};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -40,7 +39,7 @@ impl MiddlewareLayer for Etag {
}

/// Applies the `ETag` middleware to the application router.
fn apply(&self, app: AXRouter<AppContext>) -> Result<AXRouter<AppContext>> {
fn apply(&self, app: ApiRouter<AppContext>) -> Result<ApiRouter<AppContext>> {
Ok(app.layer(EtagLayer))
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/controller/middleware/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
//! not match. It serves a file, a custom not-found message, or a default HTML
//! fallback page based on the configuration.

use axum::{http::StatusCode, response::Html, Router as AXRouter};
use aide::axum::ApiRouter;
use axum::{http::StatusCode, response::Html};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::json;
use tower_http::services::ServeFile;
Expand Down Expand Up @@ -85,7 +86,7 @@ impl MiddlewareLayer for Fallback {
}

/// Applies the fallback middleware to the application router.
fn apply(&self, app: AXRouter<AppContext>) -> Result<AXRouter<AppContext>> {
fn apply(&self, app: ApiRouter<AppContext>) -> Result<ApiRouter<AppContext>> {
let app = if let Some(path) = &self.file {
app.fallback_service(ServeFile::new(path))
} else if let Some(not_found) = &self.not_found {
Expand Down
Loading