1use once_cell::sync::Lazy;
4use ring::rand::SecureRandom;
5use rquickjs::{
6 module::{Declarations, Exports, ModuleDef},
7 prelude::{Func, Opt},
8 Ctx, Function, Result, TypedArray, Value,
9};
10use uuid::Uuid;
11use uuid_simd::UuidExt;
12
13use crate::{
14 modules::{crypto::SYSTEM_RANDOM, encoding::encoder::bytes_to_hex, module::export_default},
16 utils::{
17 object::{get_bytes, get_bytes_offset_length},
18 result::ResultExt,
19 },
20};
21
22pub struct UuidModule;
23
24static ERROR_MESSAGE: &str = "Not a valid UUID";
25
26static NODE_ID: Lazy<[u8; 6]> = Lazy::new(|| {
27 let mut bytes = [0; 6];
28 SYSTEM_RANDOM.fill(&mut bytes).unwrap();
29 bytes
30});
31
32fn from_value<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Uuid> {
33 if value.is_string() {
34 Uuid::try_parse(&value.as_string().unwrap().to_string()?)
35 } else {
36 Uuid::from_slice(&get_bytes(ctx, value)?)
37 }
38 .or_throw_msg(ctx, ERROR_MESSAGE)
39}
40
41fn uuidv1() -> String {
42 Uuid::now_v1(&NODE_ID).format_hyphenated().to_string()
43}
44
45fn uuidv3<'js>(ctx: Ctx<'js>, name: String, namespace: Value<'js>) -> Result<String> {
46 let uuid = Uuid::new_v3(&from_value(&ctx, namespace)?, name.as_bytes())
47 .format_hyphenated()
48 .to_string();
49 Ok(uuid)
50}
51
52fn uuidv5<'js>(ctx: Ctx<'js>, name: String, namespace: Value<'js>) -> Result<String> {
53 let uuid = Uuid::new_v5(&from_value(&ctx, namespace)?, name.as_bytes())
54 .format_hyphenated()
55 .to_string();
56 Ok(uuid)
57}
58
59pub fn uuidv4() -> String {
60 Uuid::new_v4().format_hyphenated().to_string()
61}
62
63fn parse(ctx: Ctx<'_>, value: String) -> Result<TypedArray<u8>> {
64 let uuid = Uuid::try_parse(&value).or_throw_msg(&ctx, ERROR_MESSAGE)?;
65 let bytes = uuid.as_bytes();
66 TypedArray::<u8>::new(ctx, *bytes)
67}
68
69fn stringify<'js>(ctx: Ctx<'js>, value: Value<'js>, offset: Opt<u8>) -> Result<String> {
70 let value = get_bytes_offset_length(
71 &ctx,
72 value,
73 offset.0.map(|o| o.into()).unwrap_or_default(),
74 None,
75 )?;
76 let value = bytes_to_hex(&value);
77
78 let uuid = Uuid::try_parse_ascii(&value)
79 .or_throw_msg(&ctx, ERROR_MESSAGE)?
80 .as_hyphenated()
81 .to_string();
82
83 Ok(uuid)
84}
85
86fn validate(value: String) -> bool {
87 Uuid::parse_str(&value).is_ok()
88}
89
90fn version(ctx: Ctx<'_>, value: String) -> Result<u8> {
91 let uuid = Uuid::parse_str(&value).or_throw_msg(&ctx, ERROR_MESSAGE)?;
92 Ok(uuid.get_version().map(|v| v as u8).unwrap_or(0))
93}
94
95impl ModuleDef for UuidModule {
96 fn declare(declare: &Declarations<'_>) -> Result<()> {
97 declare.declare("v1")?;
98 declare.declare("v3")?;
99 declare.declare("v4")?;
100 declare.declare("v5")?;
101 declare.declare("parse")?;
102 declare.declare("validate")?;
103 declare.declare("stringify")?;
104 declare.declare("version")?;
105 declare.declare("NIL")?;
106 declare.declare("default")?;
107
108 Ok(())
109 }
110
111 fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
112 export_default(ctx, exports, |default| {
113 let dns_namespace = Uuid::NAMESPACE_DNS.format_hyphenated().to_string();
114 let url_namespace = Uuid::NAMESPACE_URL.format_hyphenated().to_string();
115
116 let v3_func = Function::new(ctx.clone(), uuidv3)?;
117 let v3_object = v3_func.as_object().unwrap();
118
119 let v5_func = Function::new(ctx.clone(), uuidv5)?;
120 let v5_object = v5_func.as_object().unwrap();
121
122 v3_object.set("DNS", dns_namespace.clone())?;
123 v3_object.set("URL", url_namespace.clone())?;
124 v5_object.set("DNS", dns_namespace)?;
125 v5_object.set("URL", url_namespace)?;
126
127 default.set("v1", Func::from(uuidv1))?;
128 default.set("v3", v3_func)?;
129 default.set("v4", Func::from(uuidv4))?;
130 default.set("v5", v5_func)?;
131 default.set("NIL", "00000000-0000-0000-0000-000000000000")?;
132 default.set("parse", Func::from(parse))?;
133 default.set("stringify", Func::from(stringify))?;
134 default.set("validate", Func::from(validate))?;
135 default.set("version", Func::from(version))?;
136 Ok(())
137 })
138 }
139}
140
141