1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
//! Manage web server routing
//!
//! # Example
//!
//! This example you can adding custom routes into your application by
//! implementing routes trait from [`crate::app::Hooks`] and adding your
//! endpoints to your application
//!
//! ```rust
//! # #[cfg(feature = "with-db")] {
//! use async_trait::async_trait;
//! use loco_rs::{
//! app::{AppContext, Hooks},
//! boot::{create_app, BootResult, StartMode},
//! controller::{channels::AppChannels, AppRoutes},
//! worker::Processor,
//! task::Tasks,
//! environment::Environment,
//! Result,
//! };
//! use sea_orm::DatabaseConnection;
//! use std::path::Path;
//!
//! /// this code block should be taken from the sea_orm migration model.
//! pub struct App;
//! pub use sea_orm_migration::prelude::*;
//! pub struct Migrator;
//! #[async_trait::async_trait]
//! impl MigratorTrait for Migrator {
//! fn migrations() -> Vec<Box<dyn MigrationTrait>> {
//! vec![]
//! }
//! }
//!
//! #[async_trait]
//! impl Hooks for App {
//!
//! fn app_name() -> &'static str {
//! env!("CARGO_CRATE_NAME")
//! }
//!
//! fn routes(ctx: &AppContext) -> AppRoutes {
//! AppRoutes::with_default_routes()
//! // .add_route(controllers::notes::routes())
//! }
//!
//! async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult>{
//! create_app::<Self, Migrator>(mode, environment).await
//! }
//!
//! /// Only when `channels` feature is enabled
//! fn register_channels(_ctx: &AppContext) -> AppChannels {
//! let channels = AppChannels::default();
//! //channels.register.ns("/", channels::application::on_connect);
//! channels
//! }
//!
//!
//! fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AppContext) {}
//!
//! fn register_tasks(tasks: &mut Tasks) {}
//!
//! async fn truncate(db: &DatabaseConnection) -> Result<()> {
//! Ok(())
//! }
//!
//! async fn seed(db: &DatabaseConnection, base: &Path) -> Result<()> {
//! Ok(())
//! }
//! }
//! }
//! ```
pub use app_routes::{AppRoutes, ListRoutes};
use axum::{
extract::FromRequest,
http::StatusCode,
response::{IntoResponse, Response},
};
use colored::Colorize;
pub use routes::Routes;
use serde::Serialize;
use crate::{errors::Error, Result};
mod app_routes;
mod backtrace;
#[cfg(feature = "channels")]
pub mod channels;
mod describe;
pub mod format;
#[cfg(feature = "with-db")]
mod health;
pub mod middleware;
mod ping;
mod routes;
pub mod views;
/// Create an unauthorized error with a specified message.
///
/// This function is used to generate an `Error::Unauthorized` variant with a
/// custom message.
///
/// # Errors
///
/// returns unauthorized enum
///
/// # Example
///
/// ```rust
/// use loco_rs::prelude::*;
///
/// async fn login() -> Result<Response> {
/// let valid = false;
/// if !valid {
/// return unauthorized("unauthorized access");
/// }
/// format::json(())
/// }
/// ````
pub fn unauthorized<T: Into<String>, U>(msg: T) -> Result<U> {
Err(Error::Unauthorized(msg.into()))
}
/// Return a bad request with a message
///
/// # Errors
///
/// This function will return an error result
pub fn bad_request<T: Into<String>, U>(msg: T) -> Result<U> {
Err(Error::BadRequest(msg.into()))
}
/// return not found status code
///
/// # Errors
/// Currently this function did't return any error. this is for feature
/// functionality
pub fn not_found<T>() -> Result<T> {
Err(Error::NotFound)
}
#[derive(Debug, Serialize)]
/// Structure representing details about an error.
pub struct ErrorDetail {
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl ErrorDetail {
/// Create a new `ErrorDetail` with the specified error and description.
#[must_use]
pub fn new<T: Into<String>>(error: T, description: T) -> Self {
Self {
error: Some(error.into()),
description: Some(description.into()),
}
}
/// Create an `ErrorDetail` with only an error reason and no description.
#[must_use]
pub fn with_reason<T: Into<String>>(error: T) -> Self {
Self {
error: Some(error.into()),
description: None,
}
}
}
#[derive(Debug, FromRequest)]
#[from_request(via(axum::Json), rejection(Error))]
pub struct Json<T>(pub T);
impl<T: Serialize> IntoResponse for Json<T> {
fn into_response(self) -> axum::response::Response {
axum::Json(self.0).into_response()
}
}
impl IntoResponse for Error {
/// Convert an `Error` into an HTTP response.
fn into_response(self) -> Response {
match &self {
Self::WithBacktrace {
inner,
backtrace: _,
} => {
tracing::error!(
error.msg = %inner,
error.details = ?inner,
"controller_error"
);
}
err => {
tracing::error!(
error.msg = %err,
error.details = ?err,
"controller_error"
);
}
}
let public_facing_error = match self {
Self::NotFound => (
StatusCode::NOT_FOUND,
ErrorDetail::new("not_found", "Resource was not found"),
),
Self::InternalServerError => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorDetail::new("internal_server_error", "Internal Server Error"),
),
Self::Unauthorized(err) => {
tracing::warn!(err);
(
StatusCode::UNAUTHORIZED,
ErrorDetail::new(
"unauthorized",
"You do not have permission to access this resource",
),
)
}
Self::CustomError(status_code, data) => (status_code, data),
Self::WithBacktrace { inner, backtrace } => {
println!("\n{}", inner.to_string().red().underline());
backtrace::print_backtrace(&backtrace).unwrap();
(
StatusCode::BAD_REQUEST,
ErrorDetail::with_reason("Bad Request"),
)
}
_ => (
StatusCode::BAD_REQUEST,
ErrorDetail::with_reason("Bad Request"),
),
};
(public_facing_error.0, Json(public_facing_error.1)).into_response()
}
}