# redis-om [![MIT licensed][mit-badge]][mit-url] [![Build status][gh-actions-badge]][gh-actions-url] [![Crates.io][crates-badge]][crates-url] [crates-badge]: https://img.shields.io/crates/v/redis-om.svg [crates-url]: https://crates.io/crates/redis-om [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: LICENSE [gh-actions-badge]: https://github.com/kkharji/redis-om/workflows/Continuous%20integration/badge.svg [gh-actions-url]: https://github.com/kkharji/redis-om/actions?query=workflow%3A%22Continuous+integration%22 A Rust/Redis ORM-style library that simplify the development process and reduce the amount of boilerplate code needed to build programs that leverage [redis] powerful capabilities and use cases. **Status**: *WIP, fully testsed, possible breaking changes, stay tuned* **Features** - ORM-style API to define/manipulate [redis data structures] (e.g. hashes, json, streams) using [derive macros]. - Automatic serialization/desalinization between Redis data and rust objects. - Interoperability with [serde](https://serde.rs/), e.g. using `rename`, `rename_all` or `serde`. - Nested [hash datatype](#hash) support (e.g. `list.1` or nested models `account.balance` as keys). **Usage** - [Getting Started](#getting-started) - [Using Redis's Hash Datatype](#hash) - [Using Redis's Json Datatype](#json) - [Using Redis's Stream Datatype](#stream) **Roadmap** - 0.1.0 - [x] Enable users to define and derive Hash Model with most common methods - [x] Enable users to define and derive JSON Model with most common methods - [x] Enable users to define and derive streams with managers to publish-to/read-from them. - [x] Support users to choose between asynchronous and synchronous runtime. - 0.2.0 - [ ] Enable Multi-Stream Manager Support to enable users to combine multiple `RedisModels`. - [ ] Support Serializing/deserializing `HashModel` complex fields using serde. - [ ] Support `RedisSearch` and provide query-building API. - [ ] ..... - 0.3.0 - [ ] Support validation of struct fields and enum values (most likely using [validator library]). - [ ] ..... ## Getting Started ```toml redis-om = { version = "*" } # TLS support with async-std redis-om = { version = "*", features = ["tls"] } # async support with tokio redis-om = { version = "*", features = ["tokio-comp"] } # async support with async-std redis-om = { version = "*", features = ["async-std-comp"] } # TLS and async support with tokio redis-om = { version = "*", features = ["tokio-native-tls-comp"] } # TLS support with async-std redis-om = { version = "*", features = ["async-std-tls-comp"] } ``` ## Hash ```rust ignore use redis_om::HashModel; #[derive(HashModel, Debug, PartialEq, Eq)] struct Customer { id: String, first_name: String, last_name: String, email: String, bio: Option, interests: Vec } // Now that we have a `Customer` model, let's use it to save customer data to Redis. // First, we create a new `Customer` object: let mut jane = Customer { id: "".into(), // will be auto generated when it's empty first_name: "Jane".into(), last_name: "Doe".into(), email: "jane.doe@example.com".into(), bio: Some("Open Source Rust developer".into()), interests: vec!["Books".to_string()], }; // Get client let client = redis_om::Client::open("redis://127.0.0.1/").unwrap(); // Get connection let mut conn = client.get_connection().unwrap(); // We can save the model to Redis by calling `save()`: jane.save(&mut conn).unwrap(); // Expire the model after 1 min (60 seconds) jane.expire(60, &mut conn).unwrap(); // Retrieve this customer with its primary key let jane_db = Customer::get(&jane.id, &mut conn).unwrap(); // Delete customer Customer::delete(&jane.id, &mut conn).unwrap(); assert_eq!(jane_db, jane); ``` ## Json redis-om support json data type through `redis_om::JsonModel`. It requires that the type derives `serde::Deserialize` as well as `serde::Serialize`. ```rust ignore use redis_om::JsonModel; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] struct AccountDetails { balance: String, } #[derive(JsonModel, Deserialize, Serialize, Debug, PartialEq, Eq)] struct Account { id: String, first_name: String, last_name: String, details: AccountDetails, } // Now that we have a `Account` model, let's use it to save account data to Redis. // First, we create a new `Account` object: let mut john = Account { id: "".into(), // will be auto generated when it's empty first_name: "John".into(), last_name: "Doe".into(), details: AccountDetails { balance: "1.5m".into(), } }; // Get client let client = redis_om::Client::open("redis://127.0.0.1/").unwrap(); // Get connection let mut conn = client.get_connection().unwrap(); // We can save the model to Redis by calling `save()`: john.save(&mut conn).unwrap(); // Expire the model after 1 min (60 seconds) john.expire(60, &mut conn).unwrap(); // Retrieve this account with its primary key let john_db = Account::get(&john.id, &mut conn).unwrap(); // Delete customer Account::delete(&john.id, &mut conn).unwrap(); assert_eq!(john_db, john); ``` ## Stream redis-om support json data type through `redis_om::StreamModel`. It requires that any nested type to derives `redis_om::RedisTransportValue`. ```rust ignore use redis_om::{RedisTransportValue, StreamModel}; /// An enum of room service kind #[derive(RedisTransportValue)] pub enum RoomServiceJob { Clean, ExtraTowels, ExtraPillows, FoodOrder, } /// An enum of room service kind #[derive(StreamModel)] #[redis(key = "room")] // rename stream key in redis pub struct RoomServiceEvent { status: String, room: usize, job: RoomServiceJob, } // Get client let client = redis_om::Client::open("redis://127.0.0.1/").unwrap(); // Get connection let mut conn = client.get_connection().unwrap(); // Create a new instance of Room service Event Manager with consumer group. // Note: consumer name is auto generated, // use RoomServiceEventManager::new_with_consumer_name, // for a custom name let manager = RoomServiceEventManager::new("Staff"); // Ensure the consumer group manager.ensure_group_stream(&mut conn).unwrap(); // Create new event let event = RoomServiceEvent { status: "pending".into(), room: 3, job: RoomServiceJob::Clean, }; // Publish the event to the RoomServiceEvent redis stream RoomServiceEventManager::publish(&event, &mut conn).unwrap(); // Read with optional read_count: Option, block_interval: Option let read = manager.read(None, None, &mut conn).unwrap(); // Get first incoming event let incoming_event = read.first().unwrap(); // Get first incoming event data let incoming_event_data = incoming_event.data::().unwrap(); // Acknowledge that you received the event, so other in the consumers don't get it RoomServiceEventManager::ack(manager.group_name(), &[&incoming_event.id], &mut conn).unwrap(); assert_eq!(incoming_event_data.room, event.room); ``` [derive macros]: https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros [redis data structures]: https://redis.com/redis-enterprise/data-structures/ [redis]: https://redis.com [validator library]: https://crates.io/crates/validator