#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::print_stdout)]
use std::sync::Arc;
use axum::Router;
use axum::extract::Path;
use axum::routing::get;
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
use tower_conneg::{
ErasedFormat, JsonFormat, MsgPackFormat, Negotiate, NegotiateLayer, NegotiateResponse,
ServerConfig,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
#[derive(Debug, Clone, Serialize)]
struct ApiError {
code: String,
message: String,
}
fn build_config() -> ServerConfig {
let json: Arc<dyn ErasedFormat> = Arc::new(JsonFormat);
let msgpack: Arc<dyn ErasedFormat> = Arc::new(MsgPackFormat);
ServerConfig::builder()
.formats(vec![json.clone(), msgpack])
.fallback_format(json) .build()
}
async fn get_user(
Path(id): Path<u64>,
neg: Negotiate<()>,
) -> Result<NegotiateResponse<User>, NegotiateResponse<ApiError>> {
if id == 0 {
return Err(neg.respond(ApiError {
code: "NOT_FOUND".to_string(),
message: format!("User with id {id} not found"),
}));
}
Ok(neg.respond(User {
id,
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
}))
}
async fn create_user(
req: Negotiate<CreateUserRequest>,
) -> Result<NegotiateResponse<User>, NegotiateResponse<ApiError>> {
if req.name.is_empty() {
return Err(req.respond(ApiError {
code: "VALIDATION_ERROR".to_string(),
message: "Name cannot be empty".to_string(),
}));
}
let user = User {
id: 42, name: req.name.clone(),
email: req.email.clone(),
};
Ok(req.respond(user))
}
async fn list_users(neg: Negotiate<()>) -> NegotiateResponse<Vec<User>> {
let users = vec![
User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
},
User {
id: 2,
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
},
];
neg.respond(users)
}
#[tokio::main]
async fn main() {
let config = build_config();
let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(get_user))
.layer(NegotiateLayer::new(config));
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
println!("Server running on http://127.0.0.1:3000");
println!();
println!("Try these requests:");
println!(" curl -H 'Accept: application/json' http://localhost:3000/users");
println!(" curl -H 'Accept: application/msgpack' http://localhost:3000/users/1");
println!(" curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' \\");
println!(
" -d '{{\"name\":\"Alice\",\"email\":\"alice@example.com\"}}' http://localhost:3000/users"
);
axum::serve(listener, app).await.unwrap();
}