idgen_cli/
processor_clap.rs

1use crate::cli::{build_cli, resolve_namespace, Cli, Commands, IdType, UuidFormat};
2use crate::id::{new_id, CuidVersion, IDError, IDFormat, UuidVersion};
3use crate::inspector::inspect_id;
4use clap::Parser;
5use clap_complete::generate;
6use clap_mangen::Man;
7use serde::Serialize;
8use std::io;
9use std::process;
10
11/// Exit codes following Unix conventions
12pub mod exit_codes {
13    /// Successful execution
14    pub const SUCCESS: i32 = 0;
15    /// General runtime error (e.g., ID generation failed due to system error)
16    pub const ERROR: i32 = 1;
17    /// Invalid command-line arguments or usage (e.g., missing required params)
18    pub const USAGE_ERROR: i32 = 2;
19}
20
21#[derive(Serialize)]
22struct IdOutput {
23    value: String,
24}
25
26pub fn parse_n_process() {
27    let cli = Cli::parse();
28
29    // Handle subcommands first
30    if let Some(command) = &cli.command {
31        match command {
32            Commands::Inspect { id, json } => {
33                handle_inspect(id, *json);
34                return;
35            }
36            Commands::Completions { shell } => {
37                let mut cmd = build_cli();
38                generate(*shell, &mut cmd, "idgen", &mut io::stdout());
39                return;
40            }
41            Commands::ManPage => {
42                let cmd = build_cli();
43                let man = Man::new(cmd);
44                let mut buffer: Vec<u8> = Vec::new();
45                man.render(&mut buffer)
46                    .expect("Failed to generate man page");
47                print!("{}", String::from_utf8_lossy(&buffer));
48                return;
49            }
50        }
51    }
52
53    // Show banner if requested
54    if cli.banner {
55        print_banner();
56    }
57
58    // Validate count
59    if cli.count < 1 {
60        eprintln!("Error: Count must be at least 1, got {}", cli.count);
61        process::exit(exit_codes::USAGE_ERROR);
62    }
63
64    // Convert CLI options to internal types
65    let (id_format, namespace, name) = match build_id_format(&cli) {
66        Ok(result) => result,
67        Err(msg) => {
68            eprintln!("Error: {}", msg);
69            process::exit(exit_codes::USAGE_ERROR);
70        }
71    };
72
73    // Generate IDs
74    match generate_ids(&id_format, &cli, namespace.as_deref(), name.as_deref()) {
75        Ok(_) => {}
76        Err(err) => {
77            eprintln!("Error: {}", err);
78            let exit_code = if err.is::<IDError>() {
79                exit_codes::USAGE_ERROR
80            } else {
81                exit_codes::ERROR
82            };
83            process::exit(exit_code);
84        }
85    }
86}
87
88fn handle_inspect(id: &str, json_output: bool) {
89    let result = inspect_id(id);
90
91    if json_output {
92        let json = serde_json::to_string_pretty(&result).unwrap();
93        println!("{}", json);
94    } else {
95        println!("ID: {}", id);
96        println!("Valid: {}", result.valid);
97        println!("Type: {}", result.id_type);
98        if let Some(v) = &result.version {
99            println!("Version: {}", v);
100        }
101        if let Some(v) = &result.variant {
102            println!("Variant: {}", v);
103        }
104        if let Some(ts) = &result.timestamp {
105            println!("Timestamp: {}", ts);
106        }
107    }
108
109    if !result.valid {
110        process::exit(exit_codes::ERROR);
111    }
112}
113
114fn build_id_format(cli: &Cli) -> Result<(IDFormat, Option<String>, Option<String>), String> {
115    let uuid_version = match cli.id_type {
116        IdType::Uuid1 => Some(UuidVersion::V1),
117        IdType::Uuid3 => Some(UuidVersion::V3),
118        IdType::Uuid4 => Some(UuidVersion::V4),
119        IdType::Uuid5 => Some(UuidVersion::V5),
120        _ => None,
121    };
122
123    // Handle namespace resolution for v3/v5
124    let namespace = if matches!(cli.id_type, IdType::Uuid3 | IdType::Uuid5) {
125        match &cli.namespace {
126            Some(ns) => match resolve_namespace(ns) {
127                Ok(resolved) => Some(resolved),
128                Err(e) => return Err(e),
129            },
130            None => {
131                return Err(format!(
132                    "UUID {} requires --namespace parameter. Use DNS, URL, OID, X500, or a custom UUID.",
133                    if cli.id_type == IdType::Uuid3 { "v3" } else { "v5" }
134                ));
135            }
136        }
137    } else {
138        None
139    };
140
141    let name = if matches!(cli.id_type, IdType::Uuid3 | IdType::Uuid5) {
142        match &cli.name {
143            Some(n) => Some(n.clone()),
144            None => {
145                return Err(format!(
146                    "UUID {} requires --name parameter.",
147                    if cli.id_type == IdType::Uuid3 {
148                        "v3"
149                    } else {
150                        "v5"
151                    }
152                ));
153            }
154        }
155    } else {
156        None
157    };
158
159    let format = match cli.id_type {
160        IdType::Uuid1 | IdType::Uuid3 | IdType::Uuid4 | IdType::Uuid5 => {
161            let version = uuid_version.unwrap();
162            match cli.format {
163                UuidFormat::Simple => IDFormat::Simple(version),
164                UuidFormat::Hyphenated => IDFormat::Hyphenated(version),
165                UuidFormat::Urn => IDFormat::URN(version),
166            }
167        }
168        IdType::NanoId => IDFormat::NanoID,
169        IdType::Cuid1 => IDFormat::Cuid(CuidVersion::V1),
170        IdType::Cuid2 => IDFormat::Cuid(CuidVersion::V2),
171        IdType::Ulid => IDFormat::Ulid,
172        IdType::ObjectId => IDFormat::OID,
173    };
174
175    Ok((format, namespace, name))
176}
177
178fn generate_ids(
179    id_format: &IDFormat,
180    cli: &Cli,
181    namespace: Option<&str>,
182    name: Option<&str>,
183) -> Result<(), Box<dyn std::error::Error>> {
184    let len = cli.length;
185
186    if cli.json {
187        let mut ids = Vec::new();
188        for _ in 0..cli.count {
189            let id = new_id(id_format, len, namespace, name)?;
190            ids.push(IdOutput {
191                value: format!("{}{}{}", cli.prefix, id, cli.suffix),
192            });
193        }
194        let json = serde_json::to_string_pretty(&ids)?;
195        println!("{}", json);
196    } else {
197        for i in 0..cli.count {
198            let id = new_id(id_format, len, namespace, name)?;
199            print!("{}{}{}", cli.prefix, id, cli.suffix);
200            if i < cli.count - 1 {
201                println!();
202            }
203        }
204        println!();
205    }
206
207    Ok(())
208}
209
210fn print_banner() {
211    let banner = r#" _     _
212(_) __| | __ _  ___ _ __
213| |/ _` |/ _` |/ _ \ '_ \
214| | (_| | (_| |  __/ | | |
215|_|\__,_|\__, |\___|_| |_|
216         |___/"#;
217    println!("{}", banner);
218}