use api_bones::{ApiError, ErrorCode, ProblemJson, ValidationError};
use axum::{Router, routing::get};
async fn get_booking() -> Result<String, ProblemJson> {
let err = ApiError::not_found("Booking 42 not found");
let mut problem = ProblemJson::from(err);
problem.extend("trace_id", "span-abc-123");
Err(problem)
}
async fn create_booking() -> Result<String, ProblemJson> {
let err =
ApiError::new(ErrorCode::ValidationFailed, "Invalid booking request").with_errors(vec![
ValidationError {
field: "/start_date".into(),
message: "must be in the future".into(),
rule: None,
},
ValidationError {
field: "/guest_count".into(),
message: "must be at least 1".into(),
rule: Some("min".into()),
},
]);
let mut problem = ProblemJson::from(err);
problem.extend("trace_id", "span-xyz-789");
Err(problem)
}
async fn custom_problem() -> Result<String, ProblemJson> {
let problem = ProblemJson::new(
"urn:api-bones:error:rate-limited",
"Rate Limited",
429,
"Too many requests — retry after 60 s",
)
.with_instance("urn:uuid:00000000-0000-0000-0000-000000000001");
Err(problem)
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/bookings/{id}", get(get_booking))
.route("/bookings", get(create_booking))
.route("/rate-limited", get(custom_problem));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.expect("failed to bind");
println!("Listening on http://127.0.0.1:3000");
println!("Try: curl -s http://127.0.0.1:3000/bookings/42 | jq");
println!("Try: curl -s http://127.0.0.1:3000/bookings | jq");
println!("Try: curl -s http://127.0.0.1:3000/rate-limited | jq");
axum::serve(listener, app).await.expect("server error");
}