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
11pub mod exit_codes {
13 pub const SUCCESS: i32 = 0;
15 pub const ERROR: i32 = 1;
17 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 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 if cli.banner {
55 print_banner();
56 }
57
58 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 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 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 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}