# DoLess π¦ β Procedural Macros for Data Mapping and Caching
`DoLess` is a Rust library offering **procedural macros** that simplify both
**data-to-struct mapping** and **cache integration** patterns.
It provides two main features:
- π§© **`#[derive(FromHashMap)]`** β auto-generates a type-safe
`From<HashMap<String, String>>` implementation for simple and nested structs.
- β‘ **`#[cache_it(...)]`** β injects cache lookup logic directly into your functions.
---
## π Features
### π§© Mapping Features
- β
Auto-maps from `HashMap<String, String>`
- π’ Supports types: `String`, numeric primitives, `bool`, `Option<T>`
- β Supports lists: `Vec<T>`, `Vec<Option<T>>`
- πͺ Nested structs with dot notation (`details.name`)
- β Defaults for missing fields
### β‘ Cache Macro Features
- π¦ Add `#[cache_it(...)]` to perform cache lookups automatically
- π Configurable options:
- `key = "some:key"`
- `key = format!("user:{}", id)`
- `var = redis` β custom cache instance name
- `name = cached_data` β custom binding name
- π Works with any cache backend implementing the `Cache` trait
- π Async-aware β supports async functions automatically
---
## π§ Design Intent: Lookup-Only by Default
The `#[cache_it]` macro performs **non-intrusive cache lookups**:
```rust
let cache_data = cache.get::<_>(&key);
```
Thatβs it β no automatic writes.
You decide when to cache the result:
- β
Cache only successful responses
- β
Skip caching transient or sensitive data
- β
Apply your own TTLs or invalidation logic
> This intentional design keeps `DoLess` flexible and predictable.
> Youβre in control of every cache write.
---
## π¦ Installation
```toml
[dependencies]
doless = "0.4.1"
```
Includes:
- `doless_core` β Cache trait, shared utilities
- `doless_macros` β Procedural macros
- `doless` β Public re-export crate
---
## β¨ Usage Guide
### π§© FromHashMap Example
```rust
use doless::FromHashMap;
use std::collections::HashMap;
#[derive(FromHashMap, Debug)]
struct Car {
model: String,
brand: String,
details: CarDetails,
tags: Vec<String>,
}
#[derive(FromHashMap, Debug)]
struct CarDetails {
name: String,
description: String,
}
fn main() {
let mut data = HashMap::new();
data.insert("model".into(), "GT-R".into());
data.insert("brand".into(), "Nissan".into());
data.insert("details.name".into(), "Skyline".into());
data.insert("details.description".into(), "Legendary Sports Car".into());
data.insert("tags".into(), "fast,collectible,cool".into());
let car: Car = Car::from(data);
println!("{:#?}", car);
}
```
---
### β‘ Cache Macro Example
#### Step 1: Implement the `Cache` Trait
```rust
use doless_core::cache::Cache;
use serde::{de::DeserializeOwned, Serialize};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
#[derive(Clone, Default)]
struct DummyCache {
store: Arc<Mutex<HashMap<String, String>>>,
}
impl Cache for DummyCache {
fn get<T: DeserializeOwned + Clone>(&self, key: &str) -> Option<T> {
let guard = self.store.lock().ok()?;
serde_json::from_str(guard.get(key)?).ok()
}
fn set<T: Serialize>(&self, key: &str, value: &T) {
if let Ok(json) = serde_json::to_string(value) {
if let Ok(mut map) = self.store.lock() {
map.insert(key.to_string(), json);
}
}
}
fn set_with<T: Serialize>(&self, key: &str, value: &T, extra: u32) {
// Default: fallback to simple `set`
// `extra` can be interpreted as TTL (in seconds) in real caches
println!("Setting with TTL={extra}s");
self.set(key, value);
}
}
```
---
#### Step 2: Lookup Function with Optional Write
```rust
use doless::cache_it;
use doless_core::cache::Cache;
#[cache_it(key = "user:list")]
fn get_users(cache: &impl Cache) -> Vec<String> {
// π `cache_data` is automatically generated by the macro
let cache_data: Option<Vec<String>> = cache_data;
// Return directly if cache hit
if let Some(users) = cache_data {
return users;
}
// Miss: compute, store, and return
let result = vec!["alice".into(), "bob".into()];
cache.set("user:list", &result);
result
}
```
---
### Dynamic Key Example
```rust
#[cache_it(key = format!("user:{}", id))]
fn get_user(id: u32, cache: &impl Cache) -> Option<User> {
cache_data
}
```
### Custom Variable Names
```rust
#[cache_it(var = redis, key = "session:active", name = cached_session)]
fn get_active_session(redis: &impl Cache) -> Option<Session> {
cached_session
}
```
---
## β± Extending Cache: TTL and Custom `set_with`
In `DoLess`, the `Cache` trait supports an overridable method for extra control:
```rust
fn set_with<T: Serialize>(&self, key: &str, value: &T, extra: u32) {
// Use `extra` for TTL, versioning, etc.
self.set(key, value);
}
```
You can override it to implement **time-to-live (TTL)** or other advanced behaviors.
Example extended cache:
```rust
use std::time::{Duration, Instant};
struct TTLCache {
data: Arc<Mutex<HashMap<String, (String, Instant)>>>,
}
impl Cache for TTLCache {
fn get<T: DeserializeOwned + Clone>(&self, key: &str) -> Option<T> {
let mut guard = self.data.lock().ok()?;
let (json, expiry) = guard.get(key)?.clone();
// Remove expired entries
if Instant::now() > expiry {
guard.remove(key);
return None;
}
serde_json::from_str(&json).ok()
}
fn set<T: Serialize>(&self, key: &str, value: &T) {
self.set_with(key, value, 60);
}
fn set_with<T: Serialize>(&self, key: &str, value: &T, ttl_secs: u32) {
if let Ok(json) = serde_json::to_string(value) {
let expiry = Instant::now() + Duration::from_secs(ttl_secs as u64);
if let Ok(mut map) = self.data.lock() {
map.insert(key.to_string(), (json, expiry));
}
}
}
}
```
Now you can call:
```rust
cache.set_with("user:1", &user, 120); // cache for 2 minutes
```
---
## π§ͺ Testing
Full examples and integration tests live under:
- [`tests/cache_it_test.rs`](./tests/cache_it_test.rs)
- [`tests/from_hashmap_test.rs`](./tests/from_hashmap_test.rs)
Run with:
```bash
cargo test
```
---
## π§ Roadmap
| FromHashMap with nested struct support | β
|
| Vec and Vec<Option<T>> handling | β
|
| Cache attribute macro | β
|
| Async auto-detection | β
|
| TTL + extended cache (via set_with) | β
(manual) |
| Custom derive hooks | π§ Planned |
| Error diagnostics | π§ Planned |
---
## π¦ Project Structure
| `doless_core` | Core cache trait and foundational types |
| `doless_macros` | Macro implementations for data mapping and caching |
| `doless` | Unified public API layer |
---
**DoLess β Write Less, Do More.**
π¦ Rust procedural macros that make your data + cache handling simpler, safer, and smarter.