The infrastructure layer handles external concerns like HTTP requests, database access, and authentication. It depends on both domain and application layers but not vice versa.
HTTP Controllers
Controllers handle HTTP requests using Actix-web framework and delegate business logic to application services.
AuthController
Handles authentication endpoints with automatic validation.
Zero-sized type providing authentication endpoint handlers.
Endpoints
register
pub async fn register(
service: web::Data<Arc<AuthService>>,
req: ValidatedJson<RegisterUserRequest>,
) -> ApiResult<HttpResponse>
Registers new user account with automatic validation via ValidatedJson extractor.
service
web::Data<Arc<AuthService>>
Injected authentication service from AppState
req
ValidatedJson<RegisterUserRequest>
Request body with automatic validator validation
Returns 201 Created with AuthResponse (user + token) on success
Example from source (auth_controller.rs:14-20):
pub async fn register(
service: web::Data<Arc<AuthService>>,
req: ValidatedJson<RegisterUserRequest>,
) -> ApiResult<HttpResponse> {
let response = service.register(req.0).await?;
Ok(HttpResponse::Created().json(response))
}
login
pub async fn login(
service: web::Data<Arc<AuthService>>,
req: ValidatedJson<LoginRequest>,
) -> ApiResult<HttpResponse>
Authenticates user and returns JWT token.
Returns 200 OK with AuthResponse on success
verify_admin
pub async fn verify_admin(
_service: web::Data<Arc<AuthService>>,
_admin: AdminUser,
) -> ApiResult<HttpResponse>
Verifies admin authentication using AdminUser extractor. Returns success if user has admin role.
UserController
Manages user profile and administration endpoints.
Zero-sized type providing user management endpoint handlers.
Endpoints
get_profile
pub async fn get_profile(
service: web::Data<Arc<UserService>>,
auth: AuthUser,
) -> ApiResult<HttpResponse>
Retrieves authenticated user’s own profile.
Authenticated user extracted from JWT token
get_all_users
pub async fn get_all_users(
service: web::Data<Arc<UserService>>,
_admin: AdminUser,
query: web::Query<PaginationQuery>,
) -> ApiResult<HttpResponse>
Returns paginated user list (admin only).
Ensures only admins can access this endpoint
query
web::Query<PaginationQuery>
Query parameters: page (default 1), per_page (default 20, max 100)
get_user
pub async fn get_user(
service: web::Data<Arc<UserService>>,
auth: AuthUser,
user_id: web::Path<String>,
) -> ApiResult<HttpResponse>
Retrieves specific user profile. Users can only view their own profile unless they’re admin.
Example from source (user_controller.rs:44-52):
let requested_id = user_id.into_inner();
if auth.0.sub != requested_id && !auth.0.is_admin() {
return Err(ApiError::Forbidden(
"You don't have permission to view this user".to_string(),
));
}
update_profile
pub async fn update_profile(
service: web::Data<Arc<UserService>>,
auth: AuthUser,
user_id: web::Path<String>,
req: ValidatedJson<UpdateProfileRequest>,
) -> ApiResult<HttpResponse>
Updates user profile (self only). Validates that authenticated user matches target user ID.
delete_user
pub async fn delete_user(
service: web::Data<Arc<UserService>>,
auth: AuthUser,
user_id: web::Path<String>,
) -> ApiResult<HttpResponse>
Deletes user account (self or admin).
Returns 204 No Content on success
TestItemController
Manages CRUD operations for test items.
Zero-sized type providing test item endpoint handlers.
Endpoints
create
pub async fn create(
service: web::Data<Arc<TestItemService>>,
req: ValidatedJson<CreateTestItemRequest>,
) -> ApiResult<HttpResponse>
Creates new test item.
Returns 201 Created with TestItemResponse
get_all
pub async fn get_all(
service: web::Data<Arc<TestItemService>>,
query: web::Query<PaginationQuery>,
) -> ApiResult<HttpResponse>
Retrieves paginated test items.
get_by_id
pub async fn get_by_id(
service: web::Data<Arc<TestItemService>>,
id: web::Path<String>,
) -> ApiResult<HttpResponse>
Retrieves specific test item by ID.
update
pub async fn update(
service: web::Data<Arc<TestItemService>>,
id: web::Path<String>,
req: ValidatedJson<UpdateTestItemRequest>,
) -> ApiResult<HttpResponse>
Updates test item fields.
delete
pub async fn delete(
service: web::Data<Arc<TestItemService>>,
id: web::Path<String>,
) -> ApiResult<HttpResponse>
Deletes test item.
Returns 204 No Content on success
Actix-web request extractors that validate JWT tokens and enforce role-based access control.
AuthUser
Base authenticated user extractor.
Extracts and validates JWT claims from Authorization header.
pub struct AuthUser(pub Claims);
Implements FromRequest to extract Bearer token from header and validate JWT signature.
Usage example:
async fn my_endpoint(auth: AuthUser) -> ApiResult<HttpResponse> {
let user_id = &auth.0.sub;
let email = &auth.0.email;
// ...
}
AdminUser
Admin-only extractor.
Validates JWT and enforces admin role requirement.
pub struct AdminUser(pub Claims);
Example from source (authentication.rs:32-42):
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ready(
extract_claims(req).and_then(|claims| {
if claims.is_admin() {
Ok(AdminUser(claims))
} else {
Err(ApiError::Forbidden("Admin access required".to_string()))
}
})
)
}
ModeratorUser
Moderator or admin extractor.
Validates JWT and requires admin or moderator role.
pub struct ModeratorUser(pub Claims);
Accepts users with “admin” or “moderator” roles.
PremiumUser
Premium tier extractor.
Validates JWT and requires admin, moderator, or premium role.
pub struct PremiumUser(pub Claims);
RoleUser
Generic role extractor with flexible validation.
Extracts claims with helper methods for role checking.
pub struct RoleUser(pub Claims);
impl RoleUser {
pub fn has_role(&self, role: &str) -> bool
pub fn has_any_role(&self, roles: &[&str]) -> bool
pub fn is_admin(&self) -> bool
pub fn user_id(&self) -> &str
pub fn email(&self) -> &str
pub fn role(&self) -> &str
}
Usage example:
async fn flexible_endpoint(user: RoleUser) -> ApiResult<HttpResponse> {
if user.has_any_role(&["admin", "premium"]) {
// Premium features
}
}
Persistence Layer
Repository implementations using PostgreSQL and SQLx.
PostgresUserRepository
PostgreSQL implementation of UserRepository trait.
User repository using SQLx PgPool for database access.
Constructor
pub fn new(pool: PgPool) -> Self
Implementation Details
create
async fn create(&self, user: &User) -> Result<User, ApiError>
Inserts new user record with RETURNING clause to get created entity.
Example from source (user_repository.rs:21-42):
let query = r#"
INSERT INTO users (id, email, username, password_hash, role, is_active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *
"#;
let created_user = sqlx::query_as::<_, User>(query)
.bind(&user.id)
.bind(&user.email.as_str())
.bind(&user.username.as_str())
.bind(&user.password_hash)
.bind(user.role.as_str())
.bind(user.is_active)
.bind(user.created_at)
.bind(user.updated_at)
.fetch_one(&self.pool)
.await
.map_err(|e| ApiError::DatabaseError(e.to_string()))?;
get_by_id
async fn get_by_id(&self, id: &str) -> Result<Option<User>, ApiError>
Uses query_as with fetch_optional for automatic deserialization.
get_by_email
async fn get_by_email(&self, email: &str) -> Result<Option<User>, ApiError>
Lookup by email address (takes string, not EmailAddress value object).
get_paginated
async fn get_paginated(&self, page: i32, per_page: i32) -> Result<(Vec<User>, i32), ApiError>
Uses LIMIT and OFFSET for pagination. Returns users and total count.
update
async fn update(&self, user: &User) -> Result<(), ApiError>
Updates all mutable fields and refreshes updated_at timestamp.
delete
async fn delete(&self, id: &str) -> Result<bool, ApiError>
Returns true if row was deleted, false if not found.
count
async fn count(&self) -> Result<i32, ApiError>
Returns total user count.
exists_by_email
async fn exists_by_email(&self, email: &str) -> Result<bool, ApiError>
Efficient existence check using EXISTS clause.
PostgresTestItemRepository
PostgreSQL implementation of TestItemRepository trait.
PostgresTestItemRepository
Test item repository using SQLx PgPool.
Constructor
pub fn new(pool: PgPool) -> Self
Methods
Implements all TestItemRepository trait methods:
create - Insert with RETURNING
get_by_id - Fetch by primary key
get_all - Fetch all ordered by created_at
get_paginated - Paginated fetch
update - Update fields and timestamp
delete - Remove by ID
count - Total count
All methods use query_as for automatic deserialization via TestItem’s FromRow implementation.
Helper Types
pub struct PaginationQuery {
pub page: Option<i32>,
pub per_page: Option<i32>,
}
Query parameter struct for pagination endpoints. Controllers provide defaults (page=1, per_page=20).
Design Patterns
Dependency Injection
Controllers receive services via web::Data<Arc<Service>> injected by Actix-web.
Authentication and validation logic is encapsulated in extractors (AuthUser, AdminUser, ValidatedJson).
Error Handling
All handlers return ApiResult<HttpResponse> which automatically converts errors to HTTP responses.
Repository Pattern
Database access is abstracted behind trait interfaces, allowing for testing and swapping implementations.