Files
harmony/harmony_config/examples/prompting.rs
Sylvain Tremblay fd70f9fdc8 refactor(harmony_config): rename ConfigManager → ConfigClient
"Manager" is a vague suffix, and the workspace had a name clash:
opnsense-config defines its own unrelated `ConfigManager` trait.
The type's real role is the consumer-facing facade you call to
get / set / get_or_prompt over an ordered source chain — a client
for accessing config. Renamed to `ConfigClient` (chosen over
`ConfigStore`, which collides with the existing SecretStore /
StoreSource nomenclature, and `ConfigResolver`, which undersells
the write/prompt side).

Pure mechanical rename, no behaviour change:
- `ConfigManager` → `ConfigClient`
- `ConfigManagerBuilder` → `ConfigClientBuilder`
- private static `CONFIG_MANAGER` → `CONFIG_CLIENT`
- doc prose + intra-doc links across harmony_config, its three
  examples, and examples/harmony_sso (incl. README + plan.md)

The opnsense-config `ConfigManager` trait is a different crate and
is untouched — the rename removes the cross-crate clash.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:54:19 -04:00

72 lines
2.5 KiB
Rust

//! Example demonstrating configuration prompting with harmony_config
//!
//! This example shows how to use `get_or_prompt()` to interactively
//! ask the user for configuration values when none are found.
//!
//! **Note**: This example requires a TTY to work properly since it uses
//! interactive prompting via `inquire`. Run in a terminal.
//!
//! Run with:
//! - `cargo run --example prompting` - will prompt for values interactively
//! - If config exists in SQLite, it will be used directly without prompting
use std::sync::Arc;
use harmony_config::{Config, ConfigClient, EnvSource, PromptSource, SqliteSource};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Config)]
struct UserConfig {
username: String,
email: String,
theme: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
// Namespace the SQLite file so this example's state doesn't
// collide with other harmony binaries that also use SqliteSource.
let sqlite = SqliteSource::for_namespace("harmony_config-prompting-example").await?;
let manager = ConfigClient::new(vec![
Arc::new(EnvSource),
Arc::new(sqlite),
Arc::new(PromptSource::new()),
]);
println!("UserConfig Setup");
println!("=================\n");
println!("Attempting to get UserConfig (env > sqlite > prompt)...\n");
match manager.get::<UserConfig>().await {
Ok(config) => {
println!("Found existing config:");
println!(" Username: {}", config.username);
println!(" Email: {}", config.email);
println!(" Theme: {}", config.theme);
println!("\nNo prompting needed - using stored config.");
}
Err(harmony_config::ConfigError::NotFound { .. }) => {
println!("No config found in env or SQLite.");
println!("Calling get_or_prompt() to interactively request config...\n");
let config: UserConfig = manager.get_or_prompt().await?;
println!("\nConfig received and saved to SQLite:");
println!(" Username: {}", config.username);
println!(" Email: {}", config.email);
println!(" Theme: {}", config.theme);
}
Err(e) => {
println!("Error: {:?}", e);
}
}
println!("\nConfig is persisted at ~/.local/share/harmony/config/config.db");
println!("On next run, the stored config will be used without prompting.");
Ok(())
}