use crate::cli::app::GenArgs;
use crate::core::EncodingFormat;
use crate::core::error::{IdtError, Result};
use crate::core::id::{IdGenerator, IdKind};
use crate::ids::{DISCORD_EPOCH, TWITTER_EPOCH};
use crate::ids::{NanoIdGenerator, SnowflakeGenerator, TypeIdGenerator, UuidGenerator};
use std::io::{self, Write};
pub fn execute(args: &GenArgs, json_output: bool, pretty: bool) -> Result<()> {
if json_output && args.template.is_some() {
return Err(IdtError::InvalidArgument(
"--template cannot be used with --json".into(),
));
}
if let Some(ref tpl) = args.template
&& !tpl.contains("{}")
{
eprintln!(
"warning: template does not contain '{{}}' placeholder; IDs will not appear in output"
);
}
let kind = args.id_type;
let ids = generate_ids(args, kind)?;
let mut writer: Box<dyn Write> = Box::new(io::stdout());
let format: Option<EncodingFormat> = args.format.as_ref().map(|f| f.parse()).transpose()?;
let formatted_ids: Vec<String> = if let Some(fmt) = format {
ids.iter()
.map(|id| format_id(id, &kind, fmt))
.collect::<Result<Vec<_>>>()?
} else {
ids
};
let final_ids = if let Some(ref tpl) = args.template {
formatted_ids
.iter()
.map(|id| tpl.replace("{}", id))
.collect()
} else {
formatted_ids
};
if json_output {
output_json(&mut writer, &final_ids, pretty)?;
} else {
output_plain(&mut writer, &final_ids, args.no_newline && args.count == 1)?;
}
Ok(())
}
fn generate_ids(args: &GenArgs, kind: IdKind) -> Result<Vec<String>> {
let mut ids = Vec::with_capacity(args.count);
match kind {
IdKind::Uuid | IdKind::UuidV4 => {
let version = args.uuid_version.unwrap_or(4);
let generator = match version {
1 => UuidGenerator::v1(),
4 => UuidGenerator::v4(),
6 => UuidGenerator::v6(),
7 => UuidGenerator::v7(),
_ => {
return Err(IdtError::InvalidArgument(format!(
"UUID version {} not supported for generation. Use 1, 4, 6, or 7.",
version
)));
}
};
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
IdKind::UuidV1 => {
let generator = UuidGenerator::v1();
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
IdKind::UuidV6 => {
let generator = UuidGenerator::v6();
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
IdKind::UuidV7 => {
let generator = UuidGenerator::v7();
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
IdKind::UuidNil => {
let generator = UuidGenerator::nil();
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
IdKind::UuidMax => {
let generator = UuidGenerator::max();
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
IdKind::Ulid => {
let generator = crate::ids::UlidGenerator::new();
for _ in 0..args.count {
ids.push(crate::core::id::IdGenerator::generate(&generator)?);
}
}
IdKind::NanoId => {
let mut generator = NanoIdGenerator::new();
if let Some(ref alphabet) = args.alphabet {
generator = generator.with_alphabet(alphabet);
}
if let Some(length) = args.length {
generator = generator.with_length(length);
}
for _ in 0..args.count {
ids.push(crate::core::id::IdGenerator::generate(&generator)?);
}
}
IdKind::Snowflake => {
let mut generator = SnowflakeGenerator::new();
if let Some(ref epoch_str) = args.epoch.map(|e| e.to_string()) {
let epoch = match epoch_str.to_lowercase().as_str() {
"twitter" => TWITTER_EPOCH,
"discord" => DISCORD_EPOCH,
_ => epoch_str.parse().map_err(|_| {
IdtError::InvalidArgument(format!("Invalid epoch: {}", epoch_str))
})?,
};
generator = generator.with_epoch(epoch);
} else if let Some(epoch) = args.epoch {
generator = generator.with_epoch(epoch);
}
if let Some(machine_id) = args.machine_id {
generator = generator.with_machine_id(machine_id);
}
if let Some(datacenter_id) = args.datacenter_id {
generator = generator.with_datacenter_id(datacenter_id);
}
for _ in 0..args.count {
ids.push(crate::core::id::IdGenerator::generate(&generator)?);
}
}
IdKind::ObjectId
| IdKind::Ksuid
| IdKind::Xid
| IdKind::Tsid
| IdKind::Cuid
| IdKind::Cuid2 => {
let generator = crate::ids::create_generator(kind)?;
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
IdKind::TypeId => {
let prefix = args.prefix.as_deref().unwrap_or("");
let generator = TypeIdGenerator::new(prefix);
for _ in 0..args.count {
ids.push(generator.generate()?);
}
}
_ => {
return Err(IdtError::GenerationError(format!(
"Generation not supported for: {}",
kind.name()
)));
}
}
Ok(ids)
}
fn format_id(id: &str, kind: &IdKind, format: EncodingFormat) -> Result<String> {
let parsed = crate::ids::parse_id(id, Some(*kind))?;
Ok(parsed.encode(format))
}
fn output_json(writer: &mut dyn Write, ids: &[String], pretty: bool) -> Result<()> {
let output = if ids.len() == 1 {
serde_json::json!({ "id": ids[0] })
} else {
serde_json::json!(ids)
};
if pretty {
writeln!(writer, "{}", serde_json::to_string_pretty(&output)?)?;
} else {
writeln!(writer, "{}", serde_json::to_string(&output)?)?;
}
Ok(())
}
fn output_plain(writer: &mut dyn Write, ids: &[String], no_newline: bool) -> Result<()> {
for (i, id) in ids.iter().enumerate() {
if i == ids.len() - 1 && no_newline {
write!(writer, "{}", id)?;
} else {
writeln!(writer, "{}", id)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn test_template_basic() {
let ids = ["abc123".to_string()];
let tpl = "id={}";
let result: Vec<String> = ids.iter().map(|id| tpl.replace("{}", id)).collect();
assert_eq!(result, ["id=abc123"]);
}
#[test]
fn test_template_sql() {
let ids = ["abc123".to_string()];
let tpl = "INSERT INTO users (id) VALUES ('{}');";
let result: Vec<String> = ids.iter().map(|id| tpl.replace("{}", id)).collect();
assert_eq!(result, ["INSERT INTO users (id) VALUES ('abc123');"]);
}
#[test]
fn test_template_multiple_placeholders() {
let ids = ["abc".to_string()];
let tpl = "{}-{}";
let result: Vec<String> = ids.iter().map(|id| tpl.replace("{}", id)).collect();
assert_eq!(result, ["abc-abc"]);
}
#[test]
fn test_template_no_placeholder() {
let tpl = "no placeholder here";
assert!(!tpl.contains("{}"));
}
}