buster/api/.cursor/rules/handlers.mdc

195 lines
5.5 KiB
Plaintext
Raw Normal View History

2025-03-04 06:57:05 +08:00
---
description: This is helpul docs for buildng hanlders in the project.
globs: ibs/handlers/**/*.rs
alwaysApply: false
---
# Handler Rules and Best Practices
## Overview
Handlers are the core business logic components that implement functionality used by both REST and WebSocket endpoints. They are also often used by the CLI package. This document outlines the structure, patterns, and best practices for working with handlers.
## File Structure
- `libs/handlers/src/`
- `[domain]/` - Domain-specific modules (e.g., messages, chats, files, metrics)
- `mod.rs` - Re-exports handlers and types
- `types.rs` - Domain-specific data structures
- `*_handler.rs` - Individual handler implementations
- `helpers/` - Helper functions and utilities for handlers
## Naming Conventions
- Handler files should be named with the pattern: `[action]_[resource]_handler.rs`
- Example: `get_chat_handler.rs`, `delete_message_handler.rs`
- Handler functions should follow the same pattern: `[action]_[resource]_handler`
- Example: `get_chat_handler()`, `delete_message_handler()`
- Type definitions should be clear and descriptive
- Request types: `[Action][Resource]Request`
- Response types: `[Action][Resource]Response`
## Handler Implementation Guidelines
### Function Signatures
```rust
pub async fn action_resource_handler(
// Parameters typically include:
request: ActionResourceRequest, // For REST/WS request data
user: User, // For authenticated user context
// Other contextual parameters as needed
) -> Result<ActionResourceResponse> {
// Implementation
}
```
### Error Handling
- Use `anyhow::Result<T>` for return types
- Provide descriptive error messages with context
- Handle specific error cases appropriately
- Log errors with relevant context
- Example:
```rust
match operation() {
Ok(result) => Ok(result),
Err(diesel::NotFound) => Err(anyhow!("Resource not found")),
Err(e) => {
tracing::error!("Operation failed: {}", e);
Err(anyhow!("Operation failed: {}", e))
}
}
```
### Database Operations
- Use the connection pool: `get_pg_pool().get().await?`
- Run concurrent operations when possible
- Use transactions for related operations
- Handle database-specific errors appropriately
- Example:
```rust
let pool = get_pg_pool();
let mut conn = pool.get().await?;
diesel::update(table)
.filter(conditions)
.set(values)
.execute(&mut conn)
.await?
```
### Concurrency
- Use `tokio::spawn` for concurrent operations
- Use `futures::try_join_all` for parallel processing
- Be mindful of connection pool limits
- Example:
```rust
let thread_future = tokio::spawn(async move {
// Database operation 1
});
let messages_future = tokio::spawn(async move {
// Database operation 2
});
let (thread_result, messages_result) = tokio::join!(thread_future, messages_future);
```
### Logging
- Use structured logging with `tracing`
- Include relevant context in log messages
- Log at appropriate levels (info, warn, error)
- Example:
```rust
tracing::info!(
resource_id = %id,
user_id = %user.id,
"Processing resource action"
);
```
### Type Definitions
- Use `serde` for serialization/deserialization
- Define clear, reusable types
- Use appropriate validation
- Example:
```rust
#[derive(Debug, Serialize, Deserialize)]
pub struct ResourceRequest {
pub id: Uuid,
pub name: String,
#[serde(default)]
pub options: Vec<String>,
}
```
## Integration with REST and WebSocket APIs
- Handlers should be independent of transport mechanism
- Same handler can be used by both REST and WebSocket endpoints
- Handlers should focus on business logic, not HTTP/WebSocket specifics
- Example:
```rust
// In REST route
pub async fn rest_endpoint(
Json(payload): Json<HandlerRequest>,
user: User,
) -> Result<Json<HandlerResponse>, AppError> {
let result = handler::action_resource_handler(payload, user).await?;
Ok(Json(result))
}
// In WebSocket handler
async fn ws_message_handler(message: WsMessage, user: User) -> Result<WsResponse> {
let payload: HandlerRequest = serde_json::from_str(&message.payload)?;
let result = handler::action_resource_handler(payload, user).await?;
Ok(WsResponse::new(result))
}
```
## CLI Integration
- Handler types should be reusable in CLI commands
- CLI commands should use the same handlers as the API when possible
- Example:
```rust
// In CLI command
pub fn cli_command(args: &ArgMatches) -> Result<()> {
let request = HandlerRequest {
// Parse from args
};
let result = tokio::runtime::Runtime::new()?.block_on(async {
handler::action_resource_handler(request, mock_user()).await
})?;
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}
```
## Testing
- Write unit tests for handlers
- Mock database and external dependencies
- Test error cases and edge conditions
- Example:
```rust
#[tokio::test]
async fn test_action_resource_handler() {
// Setup test data
let request = HandlerRequest { /* ... */ };
let user = mock_user();
// Call handler
let result = action_resource_handler(request, user).await;
// Assert expectations
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.field, expected_value);
}
```
## Common Patterns
- Retrieve data from database
- Process and transform data
- Interact with external services
- Return structured response
- Handle errors and edge cases
- Log relevant information
## Glob Pattern
The glob pattern for this rule is: `libs/handlers/**/*.rs`