Skip to content

nawajar/DoLess

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

22 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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,
    now supporting both sync and async 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 variable name
    • name = cached_data β€” custom binding name
  • πŸ”„ Works with any cache backend implementing Cache or AsyncCache
  • βš™ Async-aware β€” automatically inserts .await where needed

πŸ“¦ Installation

[dependencies]
doless = "0.4.1"

Includes:

  • doless_core β€” Cache and AsyncCache traits
  • doless_macros β€” Procedural macros
  • doless β€” Unified re-export crate

✨ Usage Examples

🧩 Example 1 β€” Struct Mapping with FromHashMap

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);
}

⚑ Example 2 β€” Caching (Sync)

use doless::cache_it;
use doless_core::cache::Cache;
use serde::{Serialize, de::DeserializeOwned};
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 m) = self.store.lock() {
                m.insert(key.into(), json);
            }
        }
    }
}

#[cache_it(key = "user:list")]
fn get_users(cache: &impl Cache) -> Vec<String> {
    let cache_data: Option<Vec<String>> = cache_data;
    if let Some(users) = cache_data {
        return users;
    }
    let users = vec!["alice".into(), "bob".into()];
    cache.set("user:list", &users);
    users
}

fn main() {
    let cache = DummyCache::new();
    println!("{:?}", get_users(&cache));
}

βš™ Example 3 β€” Async Caching with AsyncCache

use doless::cache_it;
use doless_core::cache::AsyncCache;
use serde::{Serialize, de::DeserializeOwned};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;

#[derive(Clone, Default)]
struct RedisCache {
    store: Arc<Mutex<HashMap<String, String>>>,
}

#[async_trait::async_trait]
impl AsyncCache for RedisCache {
    async fn get<T>(&self, key: &str) -> Option<T>
    where
        T: DeserializeOwned + Clone + Send + Sync,
    {
        let guard = self.store.lock().ok()?;
        serde_json::from_str(guard.get(key)?).ok()
    }

    async fn set<T>(&self, key: &str, value: &T)
    where
        T: Serialize + Send + Sync,
    {
        if let Ok(json) = serde_json::to_string(value) {
            if let Ok(mut map) = self.store.lock() {
                map.insert(key.to_string(), json);
            }
        }
    }

    async fn set_with_ttl<T>(&self, key: &str, value: &T, ttl_secs: u64)
    where
        T: Serialize + Send + Sync,
    {
        println!("Setting with TTL: {} seconds", ttl_secs);
        self.set(key, value).await;
    }
}

#[cache_it(key = "user:async")]
async fn get_user_async(cache: &impl AsyncCache) -> Option<String> {
    let cached: Option<String> = cache_data;
    if cached.is_some() {
        return cached;
    }

    let data = String::from("jeff");
    cache.set("user:async", &data).await;
    Some(data)
}

#[tokio::main]
async fn main() {
    let cache = RedisCache::new();
    let user = get_user_async(&cache).await;
    println!("User = {:?}", user);
}

🧠 The macro detects async fn automatically and inserts .await where needed.


🧭 Roadmap

Feature Status
FromHashMap with nested struct βœ…
Vec<T> and Vec<Option<T>> support βœ…
Synchronous cache macro βœ…
Async cache support βœ… NEW
TTL + extended cache (via set_with) βœ…
Error diagnostics and reporting 🚧 Planned

About

Rust **procedural macro** utility

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages