diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9a44545 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,65 @@ +name: Continuous integration + +on: + push: + branches: + - master + pull_request: + branches: + - master + + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +env: + CARGO_TERM_COLOR: always + REDIS_RS_REDIS_JSON_PATH: "/tmp/librejson.so" + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - run: cargo check --all-features + + test: + name: Test Suite + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + redis: + - 6.2.6-v6 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v1 + - name: Cache redis + id: cache-redis + uses: actions/cache@v2 + with: + path: | + /usr/bin/redis-cli + /usr/bin/redis-server + key: ${{ runner.os }}-redis + - name: Cache RedisJSON + id: cache-redisjson + uses: actions/cache@v2 + with: + path: | + /tmp/librejson.so + key: ${{ runner.os }}-redisjson + - name: Start Redis + uses: supercharge/redis-github-action@1.5.0 + with: + redis-image: redis/redis-stack + redis-version: ${{ matrix.redis }} + - run: cargo test + - run: cargo test --manifest-path lib/Cargo.toml + - run: cargo test --manifest-path lib/Cargo.toml --features json + - run: cargo test --manifest-path lib/Cargo.toml --features json,tokio-comp + - run: cargo test --all-features diff --git a/README.md b/README.md index cd332f6..d09e2b0 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,69 @@ -# redis-om-rust +# redis-om +[![MIT licensed][mit-badge]][mit-url] +[![Build status][gh-actions-badge]][gh-actions-url] +[![Crates.io][crates-badge]][crates-url] -The Unofficial Redis Object mapping that makes it easy to model Redis data in Rust. _inspired by [redis-om-python](https://github.com/redis/redis-om-python)_ +[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 -## State +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. -Alpha +**Status**: *WIP, fully testsed, possible breaking changes, stay tuned* -## Async Support +**Features** -To enable asynchronous clients a feature for the underlying feature need to be activated. +- 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). -```toml -# if you use tokio -redis-om = { version = "*", features = ["tokio-comp"] } +**Usage** -# if you use async-std -redis-om = { version = "*", features = ["async-std-comp"] } -``` +- [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 -## TLS Support ```toml +redis-om = { version = "*" } +# TLS support with async-std redis-om = { version = "*", features = ["tls"] } - -# if you use tokio +# 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"] } - -# if you use async-std +# TLS support with async-std redis-om = { version = "*", features = ["async-std-tls-comp"] } ``` -## Features - -- [serde](https://serde.rs/) interop annotations such as `rename`, `rename_all`, alias and many more. -- Use struct methods todo all kind of crud and redis specific operations. -- Serialize [hash](#hash) model list-like and dict-like structs as prefix keys without needing JSON (i.e. list.1, account.balance). -- Support for [json](#json) datatype -- Support for [stream](#json) datatype - -## Usage - -- [hash datatype macro usage](#Hash) -- [Json datatype macro usage](#json) -- [Stream datatype macro usage](#Hash) +## Hash -### Hash - -```rust +```rust ignore use redis_om::HashModel; #[derive(HashModel, Debug, PartialEq, Eq)] @@ -71,7 +89,7 @@ let mut jane = Customer { }; // Get client -let client = redis::Client::open("redis://127.0.0.1/").unwrap(); +let client = redis_om::Client::open("redis://127.0.0.1/").unwrap(); // Get connection let mut conn = client.get_connection().unwrap(); @@ -90,12 +108,12 @@ Customer::delete(&jane.id, &mut conn).unwrap(); assert_eq!(jane_db, jane); ``` -### Json +## 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 +```rust ignore use redis_om::JsonModel; use serde::{Deserialize, Serialize}; @@ -125,7 +143,7 @@ let mut john = Account { }; // Get client -let client = redis::Client::open("redis://127.0.0.1/").unwrap(); +let client = redis_om::Client::open("redis://127.0.0.1/").unwrap(); // Get connection let mut conn = client.get_connection().unwrap(); @@ -143,11 +161,11 @@ Account::delete(&john.id, &mut conn).unwrap(); assert_eq!(john_db, john); ``` -### Stream +## Stream redis-om support json data type through `redis_om::StreamModel`. It requires that any nested type to derives `redis_om::RedisTransportValue`. -```rust +```rust ignore use redis_om::{RedisTransportValue, StreamModel}; /// An enum of room service kind @@ -169,7 +187,7 @@ pub struct RoomServiceEvent { } // Get client -let client = redis::Client::open("redis://127.0.0.1/").unwrap(); +let client = redis_om::Client::open("redis://127.0.0.1/").unwrap(); // Get connection let mut conn = client.get_connection().unwrap(); @@ -198,25 +216,13 @@ let read = manager.read(None, None, &mut conn).unwrap(); 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 consumer group don't get it -incoming_event.ack(&mut conn).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.room, event.room); +assert_eq!(incoming_event_data.room, event.room); ``` -## Roadmap - -### 0.1.0 - -- [x] Hash Models -- [x] Json Model -- [x] Stream Model -- [x] Async support - -### 0.2.0 -- [ ] Multi Stream Manager -- [ ] stream model enum support -- [ ] serializing/deserializing hash model fields using serde for hash models -- [ ] Correctly support RedisSearch Integration with embedded types -- [ ] Internal managed connections, i.e. no requirement to pass conn around. -- [ ] Values Validation Support +[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 diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 475c037..bdd43a7 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,32 +1,40 @@ [package] name = "redis-om" -description = "The Unofficial Object mapping for Redis and Rust. Inspired by redis-om-python" +description = "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." version = "0.1.0" edition = "2021" authors = [ "kkharji "] license = "MIT" documentation = "https://docs.rs/redis-om" -homepage = "https://github.com/kkharji/redis-om-rust" -repository = "https://github.com/kkharji/redis-om-rust" +homepage = "https://github.com/kkharji/redis-om" +repository = "https://github.com/kkharji/redis-om" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = [] +default = [ "tokio-comp" ] +### Enables json model support json = [ "redis/json", "serde", "serde_json", "redis-om-macros/json" ] +##! ### Internal features +### Enables aio (used in tokio-native-tls-comp, tokio-comp) aio = [ "redis/aio", "async-trait", "redis-om-macros/aio", "futures" ] +### Enables async-tokio support tokio-comp = ["aio", "redis/tokio-comp"] +### Enables async-std support async-std-comp = ["aio", "redis/async-std-comp"] +### Enables tls support tls = ["redis/tls"] +### Enables async-tokio with tls support tokio-native-tls-comp = [ "redis/tokio-native-tls-comp" ] +### Enables async-std with tls support async-std-tls-comp = [ "redis/async-std-tls-comp" ] [dependencies] tap = { version = "1.0" } thiserror = { version = "1.0" } -redis-om-macros = { path = "../macros" } +redis-om-macros = { version = "0.1.0", path = "../macros" } redis = { version = "0.22.1" } rusty_ulid = { version = "2.0.0" } serde = { version = "1", features = [ "derive" ], optional = true } @@ -38,6 +46,3 @@ futures = { version = "0.3.26", optional = true } tokio = { version = "1.25.0", features = ["full"] } trybuild = { version = "1.0.77" } redis-swapplex = { version = "0.4.0" } - -[[example]] -name = "readme" diff --git a/lib/examples/readme.rs b/lib/examples/readme.rs deleted file mode 100644 index d0e6ca4..0000000 --- a/lib/examples/readme.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2023 kkharji -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. -use redis_om::HashModel; - -#[derive(HashModel, PartialEq, Eq, Default)] -struct Customer { - pub id: String, - pub first_name: String, - pub last_name: String, - pub email: String, - pub age: u32, - pub bio: Option, -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - let _customer = Customer::default(); - - Ok(()) -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index e20627a..25b68e4 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,8 +1,5 @@ -//! The Unofficial Redis Object mapping that makes it easy to model Redis data in Rust. _inspired by [redis-om-python](https://github.com/redis/redis-om-python)_ - #![deny(missing_docs, unstable_features)] -// #![doc = include_str!("../README.md")] - +#![doc = include_str!("../README.md")] mod hash_model; #[cfg(feature = "json")] mod json_model; @@ -72,7 +69,5 @@ pub use redis_model::RedisModel; pub use redissearch_model::RedisSearchModel; pub use stream_model::StreamModel; - - #[cfg(feature = "aio")] pub use async_trait::async_trait; diff --git a/lib/src/stream_model/async.rs b/lib/src/stream_model/async.rs index d6466a3..cb8621f 100644 --- a/lib/src/stream_model/async.rs +++ b/lib/src/stream_model/async.rs @@ -8,7 +8,7 @@ use super::reply::StreamReadReply; use super::transformers; impl Message { - pub async fn ack( + pub async fn ack( &self, conn: &mut C, ) -> RedisResult<()> { diff --git a/lib/src/stream_model/mod.rs b/lib/src/stream_model/mod.rs index 4ec89de..86842fb 100644 --- a/lib/src/stream_model/mod.rs +++ b/lib/src/stream_model/mod.rs @@ -31,7 +31,8 @@ mod cmds { cmd.arg("CREATE") .arg(S::stream_key()) .arg(s.group_name()) - .arg("$"); + .arg("$") + .arg("MKSTREAM"); Ok(cmd) } diff --git a/lib/src/stream_model/sync.rs b/lib/src/stream_model/sync.rs index eff84cc..0246da4 100644 --- a/lib/src/stream_model/sync.rs +++ b/lib/src/stream_model/sync.rs @@ -8,7 +8,7 @@ use super::reply::StreamReadReply; use super::transformers; impl Message { - pub fn ack(&self, conn: &mut C) -> RedisResult<()> { + pub fn ack(&self, conn: &mut C) -> RedisResult<()> { Data::ack(&self.group, &[&self.id], conn) } } @@ -140,4 +140,3 @@ pub trait StreamModel: Sized { .map(transformers::stream_range_to_messages)? } } - diff --git a/lib/tests/hash_model_async.rs b/lib/tests/hash_model_async.rs index 5ce7ff7..0defb2c 100644 --- a/lib/tests/hash_model_async.rs +++ b/lib/tests/hash_model_async.rs @@ -174,7 +174,7 @@ async fn expiring_keys() -> Result { customer.save(&mut conn).await?; customer.expire(1, &mut conn).await?; - std::thread::sleep(Duration::from_secs(1)); + tokio::time::sleep(Duration::from_secs(3)).await; let count = Cusotmer::all_pks(&mut conn).await?.count().await; diff --git a/lib/tests/stream_model_async.rs b/lib/tests/stream_model_async.rs index 94f060a..3270cf3 100644 --- a/lib/tests/stream_model_async.rs +++ b/lib/tests/stream_model_async.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "aio")] +#![cfg(feature = "tokio-comp")] use redis_om::{RedisTransportValue, StreamModel}; use std::error::Error; diff --git a/macros/Cargo.toml b/macros/Cargo.toml index aec399f..7ee4387 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" authors = ["kkharji "] license = "MIT" documentation = "https://docs.rs/redis-om-macros" -homepage = "https://github.com/kkharji/redis-om-rust" -repository = "https://github.com/kkharji/redis-om-rust" +homepage = "https://github.com/kkharji/redis-om" +repository = "https://github.com/kkharji/redis-om" readme = "../README.md" [lib]