doless 0.4.1

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:

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

[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

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

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

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

#[cache_it(key = format!("user:{}", id))]
fn get_user(id: u32, cache: &impl Cache) -> Option<User> {
    cache_data
}

Custom Variable Names

#[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:

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:

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:

cache.set_with("user:1", &user, 120); // cache for 2 minutes

πŸ§ͺ Testing

Full examples and integration tests live under:

Run with:

cargo test

🧭 Roadmap

Feature Status
FromHashMap with nested struct support βœ…
Vec and Vec<Option> 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.