diff --git a/Cargo.toml b/Cargo.toml index 674bffa..a138c85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,8 @@ name = "redis-cloud" version = "0.9.3" edition = "2024" -rust-version = "1.85" -authors = ["Josh Rotenberg "] +rust-version = "1.89" +authors = ["Josh Rotenberg "] license = "MIT OR Apache-2.0" repository = "https://github.com/redis-developer/redis-cloud-rs" homepage = "https://github.com/redis-developer/redis-cloud-rs" diff --git a/HARMONIZATION-PLAN.md b/HARMONIZATION-PLAN.md new file mode 100644 index 0000000..f868227 --- /dev/null +++ b/HARMONIZATION-PLAN.md @@ -0,0 +1,186 @@ +# redis-cloud Harmonization Plan + +Goal: Align redis-cloud with redis-enterprise patterns for consistency. + +## Changes Overview + +### 1. Add TypedBuilder to Request Types + +**Priority: High** + +Currently request types use manual `Option` fields: +```rust +pub struct DatabaseCreateRequest { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_limit_in_gb: Option, + // ... many more +} +``` + +Change to TypedBuilder pattern (matching redis-enterprise): +```rust +#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)] +pub struct DatabaseCreateRequest { + #[builder(setter(into))] + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub memory_limit_in_gb: Option, + // ... +} +``` + +**Files to update:** +- [ ] `src/flexible/databases.rs` - `DatabaseCreateRequest`, `DatabaseUpdateRequest`, `DatabaseImportRequest`, etc. +- [ ] `src/flexible/subscriptions.rs` - `SubscriptionCreateRequest`, `SubscriptionUpdateRequest`, etc. +- [ ] `src/fixed/databases.rs` - Fixed plan request types +- [ ] `src/fixed/subscriptions.rs` - Fixed plan request types +- [ ] `src/acl.rs` - ACL request types +- [ ] `src/users.rs` - User request types +- [ ] `src/connectivity/*.rs` - VPC peering, PSC, etc. request types + +**Dependencies to add:** +```toml +typed-builder = "0.18" +``` + +### 2. Add Error Helper Methods + +**Priority: High** + +Currently CloudError only has `is_retryable()`. Add helpers matching redis-enterprise: + +```rust +impl CloudError { + pub fn is_retryable(&self) -> bool { /* existing */ } + + // Add these: + pub fn is_not_found(&self) -> bool { + matches!(self, CloudError::NotFound { .. }) + } + + pub fn is_unauthorized(&self) -> bool { + matches!(self, CloudError::AuthenticationFailed { .. } | CloudError::Forbidden { .. }) + } + + pub fn is_server_error(&self) -> bool { + matches!(self, CloudError::InternalServerError { .. } | CloudError::ServiceUnavailable { .. }) + } + + pub fn is_timeout(&self) -> bool { + matches!(self, CloudError::ConnectionError(msg) if msg.contains("timeout")) + } + + pub fn is_rate_limited(&self) -> bool { + matches!(self, CloudError::RateLimited { .. }) + } + + pub fn is_conflict(&self) -> bool { + matches!(self, CloudError::PreconditionFailed) + } + + pub fn is_bad_request(&self) -> bool { + matches!(self, CloudError::BadRequest { .. }) + } +} +``` + +**Files to update:** +- [ ] `src/error.rs` + +### 3. Consistent Handler Method Naming + +**Priority: Medium** + +Align method names where semantically equivalent: + +| Operation | Current (Cloud) | Target | Enterprise | +|-----------|-----------------|--------|------------| +| List DBs | `get_subscription_databases` | `list` | `list` | +| Get DB | `get_subscription_database_by_id` | `get` | `get` / `info` | +| Create DB | `create_database` | `create` | `create` | +| Update DB | `update_database` | `update` | `update` | +| Delete DB | `delete_database_by_id` | `delete` | `delete` | + +**Note:** Keep subscription_id as parameter (architectural difference), but simplify method names. + +**Approach:** Add new methods with simpler names, deprecate old ones: +```rust +impl DatabaseHandler { + /// List databases in a subscription + pub async fn list(&self, subscription_id: i32) -> Result> { + // Unwrap the nested response internally + let response = self.get_subscription_databases(subscription_id, None, None).await?; + Ok(Self::extract_databases_from_response(&response)) + } + + #[deprecated(since = "0.10.0", note = "Use `list` instead")] + pub async fn get_subscription_databases(...) -> Result { + // existing implementation + } +} +``` + +**Files to update:** +- [ ] `src/flexible/databases.rs` +- [ ] `src/flexible/subscriptions.rs` +- [ ] `src/fixed/databases.rs` +- [ ] `src/fixed/subscriptions.rs` + +### 4. Simplify Return Types (New Methods) + +**Priority: Medium** + +Add simplified methods that unwrap nested containers: + +```rust +// Current: Returns nested wrapper +pub async fn get_subscription_databases(...) -> Result + +// Add: Returns direct vector +pub async fn list(&self, subscription_id: i32) -> Result> +``` + +Keep original methods for backward compatibility but encourage new patterns. + +### 5. Add Timeout Error Variant + +**Priority: Low** + +Enterprise has explicit `Timeout` variant. Consider adding to Cloud: + +```rust +pub enum CloudError { + // ... existing variants + + /// Request timed out + Timeout, +} +``` + +**Files to update:** +- [ ] `src/error.rs` +- [ ] `src/client.rs` (map timeout errors) + +## Non-Changes (Keep as Layer 2 Concern) + +These are architectural differences that Layer 2 should handle: + +- **Async task model** - `TaskStateUpdate` returns are by design (Cloud API is async) +- **Subscription context** - Required parameter is architectural (multi-tenant) +- **Nested response wrappers** - Original methods preserved for full API access + +## Testing + +- [ ] Ensure all existing tests pass +- [ ] Add tests for new TypedBuilder usage +- [ ] Add tests for error helper methods +- [ ] Add tests for new simplified methods + +## Version + +This will be a **minor version bump** (e.g., 0.9.x -> 0.10.0) due to: +- New dependency (typed-builder) +- New methods +- Deprecations (non-breaking) diff --git a/python/Cargo.toml b/python/Cargo.toml index 7201c4e..0a75b0f 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -2,8 +2,8 @@ name = "redis-cloud-py" version = "0.1.0" edition = "2024" -rust-version = "1.85" -authors = ["Josh Rotenberg "] +rust-version = "1.89" +authors = ["Josh Rotenberg "] license = "MIT OR Apache-2.0" description = "Python bindings for Redis Cloud API client" repository = "https://github.com/redis-developer/redis-cloud-rs" diff --git a/python/redis_cloud/__pycache__/__init__.cpython-313.pyc b/python/redis_cloud/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..b7a8aaf Binary files /dev/null and b/python/redis_cloud/__pycache__/__init__.cpython-313.pyc differ diff --git a/python/redis_cloud/redis_cloud.cpython-313-darwin.so b/python/redis_cloud/redis_cloud.cpython-313-darwin.so new file mode 100755 index 0000000..d7d9eb7 Binary files /dev/null and b/python/redis_cloud/redis_cloud.cpython-313-darwin.so differ diff --git a/python/tests/__pycache__/__init__.cpython-313.pyc b/python/tests/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..c6335e8 Binary files /dev/null and b/python/tests/__pycache__/__init__.cpython-313.pyc differ diff --git a/python/tests/__pycache__/test_client.cpython-313-pytest-9.0.2.pyc b/python/tests/__pycache__/test_client.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000..066798a Binary files /dev/null and b/python/tests/__pycache__/test_client.cpython-313-pytest-9.0.2.pyc differ