1use bson::oid::ObjectId;
2use cuid;
3use nanoid::nanoid;
4use std::str::FromStr;
5use ulid;
6use uuid::Uuid;
7
8#[derive(Debug)]
9pub enum IDError {
10 MissingNamespace(String),
11 MissingName(String),
12 InvalidNamespace(String),
13 CuidError(cuid::CuidError),
16}
17
18impl std::fmt::Display for IDError {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 IDError::MissingNamespace(msg) => write!(f, "{}", msg),
22 IDError::MissingName(msg) => write!(f, "{}", msg),
23 IDError::InvalidNamespace(msg) => write!(f, "{}", msg),
24 IDError::CuidError(err) => write!(f, "{}", err.to_string()), }
26 }
27}
28
29impl std::error::Error for IDError {}
30
31#[derive(Debug, Clone)]
33pub enum IDFormat {
34 Simple(UuidVersion),
35 Hyphenated(UuidVersion),
36 URN(UuidVersion),
37 OID,
38 NanoID,
39 Ulid,
40 Cuid(CuidVersion),
41}
42
43#[derive(Debug, Clone, Copy)]
45pub enum UuidVersion {
46 V1,
47 V3,
48 V4,
49 V5,
50}
51
52#[derive(Debug, Clone, Copy)]
54pub enum CuidVersion {
55 V1,
56 V2,
57}
58
59pub fn new_id(
74 id_format: &IDFormat,
75 len: Option<usize>,
76 namespace: Option<&str>,
77 name: Option<&str>,
78) -> Result<String, IDError> {
79 match id_format {
80 IDFormat::Simple(version) => Ok(generate_uuid(*version, namespace, name)?
81 .simple()
82 .to_string()),
83 IDFormat::Hyphenated(version) => Ok(generate_uuid(*version, namespace, name)?
84 .hyphenated()
85 .to_string()),
86 IDFormat::URN(version) => Ok(generate_uuid(*version, namespace, name)?.urn().to_string()),
87 IDFormat::OID => Ok(ObjectId::new().to_string()),
88 IDFormat::NanoID => {
89 let l = len.unwrap_or(21);
90 Ok(nanoid!(l))
91 }
92 IDFormat::Cuid(version) => Ok(generate_cuid(*version))?,
93 IDFormat::Ulid => Ok(ulid::Ulid::new().to_string()),
94 }
95}
96
97fn generate_uuid(
98 version: UuidVersion,
99 namespace: Option<&str>,
100 name: Option<&str>,
101) -> Result<Uuid, IDError> {
102 match version {
103 UuidVersion::V1 => Ok(Uuid::now_v1(&[1, 2, 3, 4, 5, 6])),
104 UuidVersion::V3 => {
105 let namespace = namespace.ok_or_else(||
106 IDError::MissingNamespace("UUID v3 requires --namespace parameter. Example: --namespace 6ba7b810-9dad-11d1-80b4-00c04fd430c8".to_string())
107 )?;
108 let name = name.ok_or_else(|| {
109 IDError::MissingName(
110 "UUID v3 requires --name parameter. Example: --name example.com".to_string(),
111 )
112 })?;
113 let namespace = Uuid::from_str(namespace).map_err(|_|
114 IDError::InvalidNamespace("Invalid namespace UUID format. Must be a valid UUID like 6ba7b810-9dad-11d1-80b4-00c04fd430c8.\nCommon namespaces:\n - DNS: 6ba7b810-9dad-11d1-80b4-00c04fd430c8\n - URL: 6ba7b811-9dad-11d1-80b4-00c04fd430c8".to_string())
115 )?;
116 Ok(Uuid::new_v3(&namespace, name.as_bytes()))
117 }
118 UuidVersion::V4 => Ok(Uuid::new_v4()),
119 UuidVersion::V5 => {
120 let namespace = namespace.ok_or_else(||
121 IDError::MissingNamespace("UUID v5 requires --namespace parameter. Example: --namespace 6ba7b810-9dad-11d1-80b4-00c04fd430c8".to_string())
122 )?;
123 let name = name.ok_or_else(|| {
124 IDError::MissingName(
125 "UUID v5 requires --name parameter. Example: --name example.com".to_string(),
126 )
127 })?;
128 let namespace = Uuid::from_str(namespace).map_err(|_|
129 IDError::InvalidNamespace("Invalid namespace UUID format. Must be a valid UUID like 6ba7b810-9dad-11d1-80b4-00c04fd430c8.\nCommon namespaces:\n - DNS: 6ba7b810-9dad-11d1-80b4-00c04fd430c8\n - URL: 6ba7b811-9dad-11d1-80b4-00c04fd430c8".to_string())
130 )?;
131 Ok(Uuid::new_v5(&namespace, name.as_bytes()))
132 }
133 }
134}
135
136fn generate_cuid(version: CuidVersion) -> Result<String, IDError> {
137 match version {
138 CuidVersion::V1 => cuid::cuid1().map_err(|err| IDError::CuidError(err)),
139 CuidVersion::V2 => Ok(cuid::cuid2()),
140 }
141}