Guides
Your own code
Adding your own routes next to the generated ones, using the database in them, and returning errors the Cadmus way.
The admin and the API cover a lot, but most apps need some pages or endpoints of their own.
They go in src/routes.rs, as ordinary axum routes, served next to
the generated ones.
#A route
use axum::Router;
use axum::routing::get;
pub fn router() -> Router {
Router::new().route("/health", get(|| async { "ok" }))
}
#Using the database
Pass the connection to your router. In src/main.rs:
let app = App::from_settings(settings::settings()?).await?.migrations(migrations::all());
let db = app.db().clone();
models::register(app).routes(routes::router(db)).run().await
and in src/routes.rs:
use axum::extract::{Path, State};
use axum::routing::get;
use axum::{Json, Router};
use cadmus::{AdminError, ApiResult};
use sea_orm::{DatabaseConnection, EntityTrait};
use crate::models::post;
pub fn router(db: DatabaseConnection) -> Router {
Router::new()
.route("/posts/{id}/title", get(title))
.with_state(db)
}
async fn title(State(db): State<DatabaseConnection>, Path(id): Path<i32>) -> ApiResult<Json<String>> {
let post = post::Entity::find_by_id(id)
.one(&db)
.await?
.ok_or_else(|| AdminError::NotFound("post".into()))?;
Ok(Json(post.title))
}
#Errors
Every failure in Cadmus is an AdminError, shown in the right form for where the request
went:
| Request to | An error looks like |
|---|---|
/api/... | JSON: {"error": {"code", "message", "field"}} |
/admin/... | a page in the admin's style (error.html, which you can replace) |
| anything else | plain text, or whatever your handler returns |
Unknown addresses, wrong methods (405) and crashes in a handler become proper error
responses too. Internal details (SQL, file paths, crash messages) are logged, never sent to
the visitor.
In your own handlers, return ApiResult<T> to get JSON errors. Database, storage and I/O
errors convert with ?, and AdminError::custom makes an error with your own status and
code:
if item.left == 0 {
return Err(AdminError::custom(StatusCode::CONFLICT, "out_of_stock", "sold out").into());
}
#Logging
Messages go to the terminal (standard error). log_level in the settings, or the
LOG_LEVEL variable, sets how much: error, warn, info (default), debug (also every
SQL statement) or trace. Each request is logged on one line with method, address, status
and duration. If your main installs its own tracing subscriber before run(), that one
is used instead.
#In the API documentation
Your own routes are not discovered automatically. Add them to the documentation with
DocsConfig::extend (see API documentation).