use {at_jet::{dual_format::configure_debug_keys,
prelude::*},
axum::http::StatusCode,
serde::{Deserialize,
Serialize}};
pub mod proto {
include!(concat!(env!("OUT_DIR"), "/at_jet.example.rs"));
}
use proto::{CreateUserRequest,
DeleteUserResponse,
ListUsersResponse,
User};
impl Serialize for User {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer, {
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("User", 4)?;
state.serialize_field("id", &self.id)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("email", &self.email)?;
state.serialize_field("created_at", &self.created_at)?;
state.end()
}
}
impl<'de> Deserialize<'de> for CreateUserRequest {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>, {
#[derive(Deserialize)]
struct Helper {
name: String,
email: String,
}
let helper = Helper::deserialize(deserializer)?;
Ok(CreateUserRequest {
name: helper.name,
email: helper.email,
})
}
}
impl Serialize for ListUsersResponse {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer, {
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("ListUsersResponse", 4)?;
state.serialize_field("users", &self.users)?;
state.serialize_field("total", &self.total)?;
state.serialize_field("page", &self.page)?;
state.serialize_field("page_size", &self.page_size)?;
state.end()
}
}
impl Serialize for DeleteUserResponse {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer, {
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("DeleteUserResponse", 2)?;
state.serialize_field("success", &self.success)?;
state.serialize_field("message", &self.message)?;
state.end()
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
JetServer::print_banner("basic-server", env!("CARGO_PKG_VERSION"), &[("Mode", "example")]);
let _tracing_guard = init_tracing(&TracingConfig {
level: "debug".to_string(),
env_filter_conf: vec!["at_jet=debug".to_string()],
..Default::default()
});
configure_debug_keys(vec!["dev-debug-key".to_string(), "qa-debug-key".to_string()]);
let server = JetServer::new()
.route("/health", get(health_check))
.route("/api/users", get(list_users).post(create_user))
.route("/api/users/:id", get(get_user).delete(delete_user))
.with_cors()
.with_compression()
.with_tracing();
tracing::info!("AT-Jet example server starting on http://0.0.0.0:8080");
tracing::info!("Endpoints: GET /health, GET/POST /api/users, GET/DELETE /api/users/:id");
tracing::info!("Protobuf (default) | JSON (debug): X-Debug-Format: dev-debug-key");
server.serve_with_shutdown("0.0.0.0:8080").await?;
Ok(())
}
async fn health_check() -> &'static str {
"OK"
}
async fn list_users(AcceptFormat(format): AcceptFormat) -> ApiResponse<ListUsersResponse> {
let users = vec![
User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
created_at: 1704067200,
},
User {
id: 2,
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
created_at: 1704153600,
},
];
let response = ListUsersResponse {
users,
total: 2,
page: 1,
page_size: 10,
};
ApiResponse::ok(format, response)
}
async fn get_user(
AcceptFormat(format): AcceptFormat,
Path(id): Path<i32>,
) -> std::result::Result<ApiResponse<User>, StatusCode> {
if id == 1 {
let user = User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
created_at: 1704067200,
};
Ok(ApiResponse::ok(format, user))
} else if id == 2 {
let user = User {
id: 2,
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
created_at: 1704153600,
};
Ok(ApiResponse::ok(format, user))
} else {
Err(StatusCode::NOT_FOUND)
}
}
async fn create_user(request: ApiRequest<CreateUserRequest>) -> std::result::Result<ApiResponse<User>, StatusCode> {
let user = User {
id: 100, name: request.body.name.clone(),
email: request.body.email.clone(),
created_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0),
};
Ok(request.created(user))
}
async fn delete_user(
AcceptFormat(format): AcceptFormat,
Path(id): Path<i32>,
) -> std::result::Result<ApiResponse<DeleteUserResponse>, StatusCode> {
if id > 0 && id <= 100 {
let response = DeleteUserResponse {
success: true,
message: format!("User {} deleted successfully", id),
};
Ok(ApiResponse::ok(format, response))
} else {
Err(StatusCode::NOT_FOUND)
}
}