doless 0.4.0

A Rust macro to simplify struct mapping , injects cache lookup logic directly into your functions ,and function utilities.
Documentation
# 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.3.0"
```

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

| Feature                                |   Status   |
| -------------------------------------- | :--------: |
| 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

| Crate           | Purpose                                            |
| --------------- | -------------------------------------------------- |
| `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.