idgen_cli/
id.rs

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    // There are several potential CuidError states but all of them
14    // seem to be caused by OS errors so I've just shimmed this for now
15    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()), // This isn't great but should be fine
25        }
26    }
27}
28
29impl std::error::Error for IDError {}
30
31/// Enum representing different ID formats and versions
32#[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/// Internal enum for UUID versions
44#[derive(Debug, Clone, Copy)]
45pub enum UuidVersion {
46    V1,
47    V3,
48    V4,
49    V5,
50}
51
52/// Internal enum for CUID versions
53#[derive(Debug, Clone, Copy)]
54pub enum CuidVersion {
55    V1,
56    V2,
57}
58
59/**
60 * Returns the newly generated id
61 *
62 * # Arguments
63 *
64 * * `id_format` - The format of the ID to generate
65 * * `len` - The length of the ID (only applicable for NanoID)
66 * * `namespace` - The namespace for UUID v3 and v5 (required for those versions)
67 * * `name` - The name for UUID v3 and v5 (required for those versions)
68 *
69 * # Returns
70 *
71 * A string representing the generated ID
72 */
73pub 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}