redisctl_core/lib.rs
1//! # redisctl-core
2//!
3//! Layer 2: Higher-level interface on top of redis-cloud and redis-enterprise clients.
4//!
5//! This crate provides:
6//! - **Unified error handling** - CoreError wrapping both platform errors
7//! - **Client resolution** - Shared profile, credential, endpoint, and TLS handling
8//! - **Progress callbacks** - For Cloud's async task polling
9//! - **Module resolution** - Validate Enterprise modules before creation
10//! - **Workflows** - Multi-step operations (create + wait, etc.)
11//!
12//! ## Philosophy
13//!
14//! **Don't rebuild Layer 1. Use it and add value.**
15//!
16//! - Simple operations: Use Layer 1 directly (`redis_cloud::DatabaseHandler`, etc.)
17//! - Operations with progress: Use Layer 2 workflows
18//! - Operations with validation: Use Layer 2 helpers
19//!
20//! # Architecture
21//!
22//! ```text
23//! ┌─────────────────────────────────────────────────────────────────┐
24//! │ Layer 3: Consumers │
25//! │ CLI (redisctl) MCP (redisctl-mcp) │
26//! └──────────────────────────┬──────────────────────────────────────┘
27//! │
28//! ▼
29//! ┌─────────────────────────────────────────────────────────────────┐
30//! │ Layer 2: redisctl-core │
31//! │ - Unified errors (CoreError) │
32//! │ - Client resolution (ClientResolver) │
33//! │ - Progress callbacks (poll_task) │
34//! │ - Module resolution (resolve_modules) │
35//! │ - Workflows (create_and_wait, etc.) │
36//! └──────────────────────────┬──────────────────────────────────────┘
37//! │
38//! ▼
39//! ┌─────────────────────────────────────────────────────────────────┐
40//! │ Layer 1: Raw API Clients │
41//! │ redis-cloud redis-enterprise │
42//! └─────────────────────────────────────────────────────────────────┘
43//! ```
44//!
45//! # Example Usage
46//!
47//! ```rust,ignore
48//! use redis_cloud::{CloudClient, DatabaseHandler};
49//! use redisctl_core::{poll_task, ProgressEvent};
50//! use std::time::Duration;
51//!
52//! // Simple operation: use Layer 1 directly
53//! let handler = DatabaseHandler::new(client.clone());
54//! let databases = handler.list(subscription_id).await?;
55//!
56//! // Operation with progress: use Layer 2
57//! let task = handler.create(subscription_id, &request).await?;
58//! let completed = poll_task(
59//! &client,
60//! &task.task_id.unwrap(),
61//! Duration::from_secs(600),
62//! Duration::from_secs(10),
63//! Some(Box::new(|event| {
64//! if let ProgressEvent::Polling { status, elapsed, .. } = event {
65//! println!("Status: {} ({:.0}s)", status, elapsed.as_secs());
66//! }
67//! })),
68//! ).await?;
69//! ```
70
71/// `User-Agent` sent by every redisctl HTTP client.
72///
73/// The Redis Cloud API recognises the `redisctl/` prefix as a trusted client for some operations
74/// (free-tier provisioning among them), so all consumers — CLI and MCP alike — must send it.
75pub const USER_AGENT: &str = concat!("redisctl/", env!("CARGO_PKG_VERSION"));
76
77/// Bound and flatten text from an upstream service before it reaches an error message.
78///
79/// Those messages are read by agents as well as people, so third-party text must not arrive with
80/// newlines or control characters, or at arbitrary length.
81pub(crate) fn bound_upstream_text(text: &str) -> String {
82 const MAX: usize = 200;
83 let flattened: String = text
84 .chars()
85 .map(|c| if c.is_control() { ' ' } else { c })
86 .take(MAX)
87 .collect();
88 let trimmed = flattened.trim().to_string();
89 if text.chars().count() > MAX {
90 format!("{trimmed}…")
91 } else {
92 trimmed
93 }
94}
95
96pub mod auth;
97pub mod clients;
98pub mod config;
99pub mod error;
100pub mod progress;
101
102pub mod cloud;
103pub mod enterprise;
104
105// Re-export commonly used items
106pub use auth::{
107 AuthError, CapiKey, CloudAuthenticator, DeviceAuthorization, DeviceFlowClient,
108 LoopbackFlowClient, MintedCredentials, SmAccount, SmApiClient, SmUser, TokenSet,
109};
110pub use error::{CoreError, Result};
111pub use progress::{ProgressCallback, ProgressEvent, poll_task};
112
113// Re-export config types for convenience
114pub use clients::{
115 ClientResolutionError, ClientResolver, ResolvedCloudConnection, ResolvedEnterpriseConnection,
116};
117pub use config::{
118 CloudAuthConfig, Config, ConfigError, CredentialStorage, CredentialStore, DeploymentType,
119 EnvironmentOverrides, Profile, ProfileCredentials,
120};
121
122// Re-export Layer 1 for convenience (but consumers can also import directly)
123pub use redis_cloud;
124pub use redis_enterprise;