# AGENTS.md - br-cache
> Redis cache abstraction library for Rust. Unified interface for cache operations.
## Quick Reference
```bash
# Build
cargo build
cargo build --release
# Test (requires Redis on localhost:6379)
cargo test
cargo test <test_name> -- --nocapture # Single test with output
# Lint & Format
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --check
# Pre-publish
cargo package --list
cargo publish --dry-run
# Run examples
cargo run --example index
cargo run --example redis_pool
cargo run --example subscribe
```
### Redis Setup (Required)
```bash
docker run -d --name dev-redis -p 6379:6379 redis:7-alpine redis-server --appendonly yes
```
## Structure
```
br-cache/
├── src/
│ ├── lib.rs # Cache enum + CacheBase trait + API forwarding (monolithic)
│ ├── redis.rs # Redis implementation (682 lines)
│ └── config.rs # Config, Connection, CacheMode structs
├── tests/
│ └── redis.rs # Integration tests only (no unit tests in src/)
├── examples/
│ ├── index.rs # Comprehensive usage
│ └── config.toml
└── Cargo.toml # Note: excludes examples/tests from publish
```
## Where to Look
| Add cache operation | `src/lib.rs` (trait) + `src/redis.rs` (impl) | Add to both |
| Add new backend | `src/lib.rs` (Cache enum) | Follow Redis pattern |
| Configuration | `src/config.rs` | Connection, CacheMode |
| Test new feature | `tests/redis.rs` | Use `init_0` or `init_1` helper |
## Code Conventions
### Language
- **Comments in Chinese** - maintain consistency
- **Error messages in Chinese** - `Err(format!("操作失败: {e}"))`
### Naming (Domain Prefixes)
| `key_*` | Key ops | exists, del, ttl, set_expireat |
| `set_*` | Set ops | add, get, delete, count, get_sinter |
| `list_*` | List ops | lpush, rpush, lpop, rpop, len, range |
| `hash_*` | Hash ops | get, add, delete, get_field_value |
| `geo_*` | Geo ops | add, get, dist, radius |
| `stream_*` | Stream ops | add, get, del, group_* |
### Error Handling
- Return `Result<T, String>` (not custom error types)
- Use `?` for propagation
- Pattern: `match data { Ok(e) => Ok(e), Err(e) => Err(format!("描述: {e}")) }`
### Types
- Use `JsonValue` from `json` crate (NOT serde_json)
- Use `object!{}` and `array![]` macros
- Use `once_cell::sync::Lazy` for statics
- Use `RwLock` for global state
### Architecture Pattern
```rust
// 1. Trait in lib.rs defines interface
pub trait CacheBase {
fn key_exists(&mut self, key: &str) -> Result<bool, String>;
}
// 2. Impl in redis.rs
impl CacheBase for Redis { ... }
// 3. Enum wrapper in lib.rs forwards calls
impl Cache {
pub fn key_exists(&mut self, key: &str) -> Result<bool, String> {
Ok(match self {
Cache::Redis(e) => e.key_exists(key)?,
Cache::None => return Err("error".to_string()),
})
}
}
```
## Testing Conventions
### Test Structure (Integration Only)
```rust
#[cfg(test)]
mod test {
// Helper: from config file
fn init_0() -> Cache {
unsafe { env::set_var("RUST_LOG", "debug"); }
let _ = env_logger::try_init();
let config = Config::create(PathBuf::new().join("examples").join("config.toml"), true);
Cache::new(config)
}
// Helper: inline connection
fn init_1() -> Cache {
unsafe { env::set_var("RUST_LOG", "debug"); }
let _ = env_logger::try_init();
Cache::create("", Connection { ... })
}
#[test]
fn test_name() -> Result<(), String> {
let mut cache = init_0();
// Use println! for debug, assertions often commented out
Ok(())
}
}
```
### Test Requirements
- Redis on `127.0.0.1:6379`
- Return `Result<(), String>` from tests
- Use `init_0` (config) or `init_1` (inline) helpers
- Debug with `println!` (visible with `--nocapture`)
## Anti-Patterns & Warnings
### Unimplemented Methods (todo!())
| `geo_dist` | redis.rs:503 | Unimplemented |
| `geo_radius` | redis.rs:507 | Unimplemented |
| `stream_group_del_user` | redis.rs:577 | Unimplemented |
### Unsafe Usage
- `unsafe { env::set_var(...) }` in tests/examples for logging
- Required due to Rust 2024 env var safety rules
- No justification comments currently
### Missing Configs
- No `.rustfmt.toml` - uses default formatting
- No `clippy.toml` - uses default lints
- No CI/CD workflows - manual testing only
## Key Dependencies
| `redis` | Client (geospatial, r2d2, disable-client-setinfo) |
| `json` | JSON via `object!`/`array!` macros |
| `r2d2` | Connection pooling (64 max, 2s timeout) |
| `once_cell` | Lazy static initialization |
| `tokio` | Async runtime (examples only) |
## Common Patterns
```rust
// Initialize from config
let config = Config::create(PathBuf::from("config.toml"), true);
let mut cache = Cache::new(config);
// Initialize direct
let mut cache = Cache::create("name", Connection {
mode: CacheMode::Redis,
hostname: "127.0.0.1".to_string(),
hostport: "6379".to_string(),
userpass: "".to_string(),
});
// Database selection + chaining
cache.db(0).add("key", value, 60)?;
cache.connection("other").db(1).get("key")?;
```