Skip to content

feat: intent-based cache API#19

Open
27Bslash6 wants to merge 2 commits intomainfrom
feat/intent-api
Open

feat: intent-based cache API#19
27Bslash6 wants to merge 2 commits intomainfrom
feat/intent-api

Conversation

@27Bslash6
Copy link
Copy Markdown
Contributor

@27Bslash6 27Bslash6 commented Apr 26, 2026

Summary

  • Adds CacheKit::minimal(), ::production(), ::encrypted(), ::io() factory methods
  • Each pre-configures backend, L1, encryption, and TTL for a specific use case
  • Returns CacheKitBuilder so callers can override any default

Intent Configuration

Intent Backend L1 Encryption Default TTL
minimal Redis Off No 300s
production Redis On (1000) No 600s
encrypted Redis On (1000) AES-256-GCM 600s
io CachekitIO On (1000) No 3600s

Design decisions

  • Redis intents are async (eager connect via fred); io() is sync (HTTP)
  • Named encrypted not secureCacheKit::secure() already exists (returns SecureCache)
  • Feature-gated: redis for Redis intents, cachekitio for io, encryption for encrypted
  • No local() intent — Python's value comes from skipping serialization (stores raw objects); Rust can't replicate without a different API. Use moka directly.

Test plan

  • 8 intent tests (builder validation, no network)
  • 116 total tests pass (default features)
  • 116 total tests pass (unsync feature)
  • clippy clean
  • Subset feature combos compile: --features redis, --features cachekitio

Closes #13

Summary by CodeRabbit

  • New Features
    • Added preset cache configuration intents: minimal (Redis, 300s TTL, no L1), production (Redis, 600s TTL, L1 enabled), encrypted (Redis + encryption, L1), and io (HTTP backend, 1h TTL, L1), with builder customization.
  • Tests
    • Added integration tests validating intent builders, Redis/encryption input checks, and io intent behavior (including overrides).

Pre-configured factory methods that declare caching intent rather than
manually composing backends, L1, and encryption:

  CacheKit::minimal(url).await?      — Redis, no L1, 300s TTL
  CacheKit::production(url).await?   — Redis + L1, 600s TTL
  CacheKit::encrypted(url, key).await? — Redis + L1 + AES-256-GCM, 600s TTL
  CacheKit::io(api_key)?             — SaaS + L1, 3600s TTL

Each returns a CacheKitBuilder so callers can override any default.
Redis intents are async (eager connect via fred). io() is sync (HTTP).

Named `encrypted` instead of `secure` to avoid collision with the
existing CacheKit::secure() method that returns SecureCache.

Closes #13
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

Adds a new intents module exposing four preset factory methods on CacheKit that return configured CacheKitBuilder instances and perform eager backend construction/validation; includes platform-aware shared-backend wrappers and a new integration test suite.

Changes

Cohort / File(s) Summary
Intent Module
crates/cachekit/src/intents.rs
New module implementing intent factory methods: CacheKit::minimal, ::production, ::encrypted (with encryption setup), and ::io (cachekit.io). Builds backends, performs eager connect/validation, sets default TTLs and L1 defaults, and surfaces backend/build errors as CachekitError. Uses platform-aware shared-backend wrapper (Arc vs Rc) via feature gates.
Crate Root
crates/cachekit/src/lib.rs
Added a private mod intents; declaration to include the new intents module.
Integration Tests
crates/cachekit/tests/intent_tests.rs
New tests covering Redis intent URL parsing/validation, encryption master-key length checks, and io intent builder behavior (valid/invalid API key, builder overrides). Tests gated by relevant features and exclude wasm32 where applicable.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer
  participant CK as CacheKit::intent
  participant Builder as CacheKitBuilder
  participant Backend as BackendFactory
  participant Redis as Redis (external)
  participant IO as cachekit.io (external)

  rect rgba(200,230,255,0.5)
  Dev->>CK: call CacheKit::production(redis_url)
  CK->>Backend: construct Redis backend (platform wrapper)
  Backend->>Redis: connect() (eager)
  Redis-->>Backend: connection result / error
  Backend-->>Builder: return configured builder (TTL=600, L1 cap=1000)
  Builder-->>Dev: Result<CacheKitBuilder, CachekitError>
  end
Loading
sequenceDiagram
  participant Dev as Developer
  participant CK as CacheKit::encrypted
  participant Builder as CacheKitBuilder
  participant Backend as BackendFactory
  participant Redis as Redis (external)
  participant Crypto as encryption_from_bytes

  rect rgba(230,245,200,0.5)
  Dev->>CK: call CacheKit::encrypted(redis_url, master_key)
  CK->>Crypto: validate/derive encryption using master_key
  Crypto-->>CK: encryption config / error
  CK->>Backend: construct Redis backend
  Backend->>Redis: connect() (eager)
  Redis-->>Backend: connection result / error
  Backend-->>Builder: configured builder (TTL=600, L1 cap=1000, encryption)
  Builder-->>Dev: Result<CacheKitBuilder, CachekitError>
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 New intents hop in, presets all set,
minimal for speed, production for stead,
encrypted keeps secrets snug and tight,
io connects outward, day or night.
Hooray — less boilerplate, more delight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: intent-based cache API' directly and concisely describes the main change: adding intent-based factory methods to CacheKit that provide pre-configured cache builders.
Linked Issues check ✅ Passed The PR implements all four required intents (minimal, production, encrypted, io) with correct backend configuration, feature gating, TTL defaults, L1 settings, and encryption support as specified in issue #13.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the intent API: new intents module with four factory methods, module registration in lib.rs, and comprehensive integration tests validating intent behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/intent-api

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/cachekit/src/lib.rs`:
- Around line 38-39: Update the top-level module doc to match the new intent API
name: replace the outdated reference to CacheKit::secure with
CacheKit::encrypted in the module comment that lists intent-based cache presets
(the line mentioning `CacheKit::minimal`, `::production`, `::secure`, `::io`).
Verify the doc string near the `mod intents;` declaration references the actual
constructor names implemented (e.g., `CacheKit::encrypted`) so the documentation
accurately reflects the API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd51bfc5-8851-407f-a72f-f28ee587bb77

📥 Commits

Reviewing files that changed from the base of the PR and between c94eab5 and a936ace.

📒 Files selected for processing (3)
  • crates/cachekit/src/intents.rs
  • crates/cachekit/src/lib.rs
  • crates/cachekit/tests/intent_tests.rs

Comment thread crates/cachekit/src/lib.rs Outdated
- Fix doc comment in lib.rs: ::secure → ::encrypted
- Fix master key doc: "at least 16 bytes" → "at least 32 bytes"
- DRY: replace 4 cfg-gated wrap_redis/wrap_cachekitio fns with single
  generic wrap() (2 cfg variants instead of 4)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/cachekit/src/intents.rs (1)

60-69: Extract shared Redis eager-connect setup to avoid drift.

The Redis bootstrap block is duplicated three times. A small helper will keep behavior aligned and reduce maintenance risk.

♻️ Proposed refactor
+#[cfg(feature = "redis")]
+async fn connected_redis_backend(redis_url: &str) -> Result<SharedBackend, CachekitError> {
+    let backend = crate::backend::redis::RedisBackend::builder()
+        .url(redis_url)
+        .build()?;
+    backend.connect().await?;
+    Ok(wrap(backend))
+}
+
 #[cfg(feature = "redis")]
 pub async fn minimal(redis_url: &str) -> Result<CacheKitBuilder, CachekitError> {
-    let backend = crate::backend::redis::RedisBackend::builder()
-        .url(redis_url)
-        .build()?;
-    drop(backend.connect().await?);
+    let backend = connected_redis_backend(redis_url).await?;
 
     Ok(CacheKitBuilder::default()
-        .backend(wrap(backend))
+        .backend(backend)
         .default_ttl(Duration::from_secs(300))
         .no_l1())
 }
 
 #[cfg(feature = "redis")]
 pub async fn production(redis_url: &str) -> Result<CacheKitBuilder, CachekitError> {
-    let backend = crate::backend::redis::RedisBackend::builder()
-        .url(redis_url)
-        .build()?;
-    drop(backend.connect().await?);
+    let backend = connected_redis_backend(redis_url).await?;
 
     Ok(CacheKitBuilder::default()
-        .backend(wrap(backend))
+        .backend(backend)
         .default_ttl(Duration::from_secs(600))
         .l1_capacity(1000))
 }
 
 #[cfg(all(feature = "redis", feature = "encryption"))]
 pub async fn encrypted(
     redis_url: &str,
     master_key: &[u8],
 ) -> Result<CacheKitBuilder, CachekitError> {
-    let backend = crate::backend::redis::RedisBackend::builder()
-        .url(redis_url)
-        .build()?;
-    drop(backend.connect().await?);
+    let backend = connected_redis_backend(redis_url).await?;
 
     CacheKitBuilder::default()
-        .backend(wrap(backend))
+        .backend(backend)
         .default_ttl(Duration::from_secs(600))
         .l1_capacity(1000)
         .encryption_from_bytes(master_key, "default")
 }

Also applies to: 96-105, 141-151

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cachekit/src/intents.rs` around lines 60 - 69, Extract the duplicated
Redis bootstrap into a single async helper (e.g., async fn
build_eager_redis_backend(redis_url: &str) ->
Result<crate::backend::redis::RedisBackend, E>) that encapsulates
RedisBackend::builder().url(redis_url).build()? followed by
backend.connect().await? (drop the connection) and returns the built backend;
then replace each duplicated block that calls RedisBackend::builder() / build()
/ connect().await? in intents.rs (including the spots currently using
wrap(backend)) with a call to this helper and preserve existing error
propagation and wrapping (wrap(backend)); keep the helper signature async and
returning the same error type so callers can use ? without changing call sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/cachekit/src/intents.rs`:
- Around line 60-63: The code currently constructs Redis backends with the same
pattern (RedisBackend::builder().url(...).build() and an eager connect().await)
which fails to encode intent-specific resilience (minimal vs
production/encrypted); update each construction site to apply intent-specific
configuration instead of identical builds: use
RedisBackend::builder().url(...).retry_policy(...) / .circuit_breaker(...) for
production, add TLS options for encrypted (e.g., .tls_enabled(true) or
equivalent), and use a lightweight health check or deferred connect for minimal
(avoid forcing connect().await); remove or replace the unconditional
drop(connect().await?) calls and ensure the builder methods (retry_policy,
circuit_breaker, tls_enabled or similar) are used on the RedisBackend::builder()
invocations to encode semantics for minimal/production/encrypted intents.

---

Nitpick comments:
In `@crates/cachekit/src/intents.rs`:
- Around line 60-69: Extract the duplicated Redis bootstrap into a single async
helper (e.g., async fn build_eager_redis_backend(redis_url: &str) ->
Result<crate::backend::redis::RedisBackend, E>) that encapsulates
RedisBackend::builder().url(redis_url).build()? followed by
backend.connect().await? (drop the connection) and returns the built backend;
then replace each duplicated block that calls RedisBackend::builder() / build()
/ connect().await? in intents.rs (including the spots currently using
wrap(backend)) with a call to this helper and preserve existing error
propagation and wrapping (wrap(backend)); keep the helper signature async and
returning the same error type so callers can use ? without changing call sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4586e92-1b1a-4b65-9ee8-e2db7cd62f7b

📥 Commits

Reviewing files that changed from the base of the PR and between a936ace and 1ebbf97.

📒 Files selected for processing (2)
  • crates/cachekit/src/intents.rs
  • crates/cachekit/src/lib.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/cachekit/src/lib.rs

Comment on lines +60 to +63
let backend = crate::backend::redis::RedisBackend::builder()
.url(redis_url)
.build()?;
drop(backend.connect().await?);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Intent resilience behavior is not differentiated.

Line 60, Line 96, and Line 141 all construct Redis the same way (builder().url().build() + eager connect). This does not encode the required intent differences for circuit-breaker/retry behavior, so minimal vs production/encrypted cannot reliably match the stated objective semantics.

Also applies to: 96-99, 141-144

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cachekit/src/intents.rs` around lines 60 - 63, The code currently
constructs Redis backends with the same pattern
(RedisBackend::builder().url(...).build() and an eager connect().await) which
fails to encode intent-specific resilience (minimal vs production/encrypted);
update each construction site to apply intent-specific configuration instead of
identical builds: use RedisBackend::builder().url(...).retry_policy(...) /
.circuit_breaker(...) for production, add TLS options for encrypted (e.g.,
.tls_enabled(true) or equivalent), and use a lightweight health check or
deferred connect for minimal (avoid forcing connect().await); remove or replace
the unconditional drop(connect().await?) calls and ensure the builder methods
(retry_policy, circuit_breaker, tls_enabled or similar) are used on the
RedisBackend::builder() invocations to encode semantics for
minimal/production/encrypted intents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: intent-based cache API (CacheKit::minimal, ::production, ::secure, ::io)

1 participant