Skip to main content

config_commands/
config_commands.rs

1//! Embeds the reusable `config-*` subcommands in an application CLI.
2
3use std::{
4    fs, io,
5    path::PathBuf,
6    time::{SystemTime, UNIX_EPOCH},
7};
8
9use clap::{Parser, Subcommand};
10use confique::Config;
11use rust_config_tree::{
12    ConfigSchema,
13    cli::{ConfigCommand, handle_config_command},
14    config::load_config,
15};
16use schemars::JsonSchema;
17
18#[derive(Debug, Parser)]
19#[command(name = "config-commands")]
20struct Cli {
21    #[arg(long)]
22    config: Option<PathBuf>,
23
24    #[command(subcommand)]
25    command: Option<Command>,
26}
27
28#[derive(Debug, Subcommand)]
29enum Command {
30    Run,
31
32    /// Flatten the crate-provided config commands into this example CLI.
33    #[command(flatten)]
34    Config(ConfigCommand),
35}
36
37#[derive(Debug, Config, JsonSchema, ConfigSchema)]
38pub struct AppConfig {
39    #[config(default = [])]
40    pub include: Vec<PathBuf>,
41
42    #[config(default = "demo")]
43    pub mode: String,
44
45    #[config(nested)]
46    pub server: ServerConfig,
47}
48
49#[derive(Debug, Config, JsonSchema)]
50pub struct ServerConfig {
51    #[config(default = "127.0.0.1")]
52    pub bind: String,
53
54    #[config(default = 8080)]
55    pub port: u16,
56}
57
58/// Parses the example CLI and dispatches either app run or config commands.
59fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
60    let cli = Cli::parse();
61    let config_path = match &cli.config {
62        Some(path) => path.clone(),
63        None => write_demo_config()?,
64    };
65
66    match cli.command.unwrap_or(Command::Run) {
67        Command::Run => {
68            let config = load_config::<AppConfig>(&config_path)?;
69            println!("config path: {}", config_path.display());
70            println!("include count: {}", config.include.len());
71            println!("mode: {}", config.mode);
72            println!("server bind: {}", config.server.bind);
73            println!("server port: {}", config.server.port);
74        }
75        Command::Config(command) => {
76            handle_config_command::<Cli, AppConfig>(command, &config_path)?;
77        }
78    }
79
80    Ok(())
81}
82
83/// Creates a minimal config file used when `--config` is omitted.
84fn write_demo_config() -> io::Result<PathBuf> {
85    let dir = temp_example_dir("config-commands")?;
86    let root_config = dir.join("config.yaml");
87
88    fs::write(
89        &root_config,
90        r#"
91mode: local
92server:
93  bind: 0.0.0.0
94  port: 3000
95"#
96        .trim_start(),
97    )?;
98
99    Ok(root_config)
100}
101
102/// Creates a unique temporary directory for one example run.
103fn temp_example_dir(name: &str) -> io::Result<PathBuf> {
104    let nanos = SystemTime::now()
105        .duration_since(UNIX_EPOCH)
106        .unwrap_or_default()
107        .as_nanos();
108    let dir = std::env::temp_dir().join(format!("rust-config-tree-{name}-{nanos}"));
109    fs::create_dir_all(&dir)?;
110    Ok(dir)
111}