use once_cell::sync::Lazy;
use ring::rand::SecureRandom;
use rquickjs::{
module::{Declarations, Exports, ModuleDef},
prelude::{Func, Opt},
Ctx, Function, Result, TypedArray, Value,
};
use uuid::Uuid;
use uuid_simd::UuidExt;
use crate::{
modules::{crypto::SYSTEM_RANDOM, encoding::encoder::bytes_to_hex, module::export_default},
utils::{
object::{get_bytes, get_bytes_offset_length},
result::ResultExt,
},
};
pub struct UuidModule;
static ERROR_MESSAGE: &str = "Not a valid UUID";
static NODE_ID: Lazy<[u8; 6]> = Lazy::new(|| {
let mut bytes = [0; 6];
SYSTEM_RANDOM.fill(&mut bytes).unwrap();
bytes
});
fn from_value<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Uuid> {
if value.is_string() {
Uuid::try_parse(&value.as_string().unwrap().to_string()?)
} else {
Uuid::from_slice(&get_bytes(ctx, value)?)
}
.or_throw_msg(ctx, ERROR_MESSAGE)
}
fn uuidv1() -> String {
Uuid::now_v1(&NODE_ID).format_hyphenated().to_string()
}
fn uuidv3<'js>(ctx: Ctx<'js>, name: String, namespace: Value<'js>) -> Result<String> {
let uuid = Uuid::new_v3(&from_value(&ctx, namespace)?, name.as_bytes())
.format_hyphenated()
.to_string();
Ok(uuid)
}
fn uuidv5<'js>(ctx: Ctx<'js>, name: String, namespace: Value<'js>) -> Result<String> {
let uuid = Uuid::new_v5(&from_value(&ctx, namespace)?, name.as_bytes())
.format_hyphenated()
.to_string();
Ok(uuid)
}
pub fn uuidv4() -> String {
Uuid::new_v4().format_hyphenated().to_string()
}
fn parse(ctx: Ctx<'_>, value: String) -> Result<TypedArray<u8>> {
let uuid = Uuid::try_parse(&value).or_throw_msg(&ctx, ERROR_MESSAGE)?;
let bytes = uuid.as_bytes();
TypedArray::<u8>::new(ctx, *bytes)
}
fn stringify<'js>(ctx: Ctx<'js>, value: Value<'js>, offset: Opt<u8>) -> Result<String> {
let value = get_bytes_offset_length(
&ctx,
value,
offset.0.map(|o| o.into()).unwrap_or_default(),
None,
)?;
let value = bytes_to_hex(&value);
let uuid = Uuid::try_parse_ascii(&value)
.or_throw_msg(&ctx, ERROR_MESSAGE)?
.as_hyphenated()
.to_string();
Ok(uuid)
}
fn validate(value: String) -> bool {
Uuid::parse_str(&value).is_ok()
}
fn version(ctx: Ctx<'_>, value: String) -> Result<u8> {
let uuid = Uuid::parse_str(&value).or_throw_msg(&ctx, ERROR_MESSAGE)?;
Ok(uuid.get_version().map(|v| v as u8).unwrap_or(0))
}
impl ModuleDef for UuidModule {
fn declare(declare: &Declarations<'_>) -> Result<()> {
declare.declare("v1")?;
declare.declare("v3")?;
declare.declare("v4")?;
declare.declare("v5")?;
declare.declare("parse")?;
declare.declare("validate")?;
declare.declare("stringify")?;
declare.declare("version")?;
declare.declare("NIL")?;
declare.declare("default")?;
Ok(())
}
fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
export_default(ctx, exports, |default| {
let dns_namespace = Uuid::NAMESPACE_DNS.format_hyphenated().to_string();
let url_namespace = Uuid::NAMESPACE_URL.format_hyphenated().to_string();
let v3_func = Function::new(ctx.clone(), uuidv3)?;
let v3_object = v3_func.as_object().unwrap();
let v5_func = Function::new(ctx.clone(), uuidv5)?;
let v5_object = v5_func.as_object().unwrap();
v3_object.set("DNS", dns_namespace.clone())?;
v3_object.set("URL", url_namespace.clone())?;
v5_object.set("DNS", dns_namespace)?;
v5_object.set("URL", url_namespace)?;
default.set("v1", Func::from(uuidv1))?;
default.set("v3", v3_func)?;
default.set("v4", Func::from(uuidv4))?;
default.set("v5", v5_func)?;
default.set("NIL", "00000000-0000-0000-0000-000000000000")?;
default.set("parse", Func::from(parse))?;
default.set("stringify", Func::from(stringify))?;
default.set("validate", Func::from(validate))?;
default.set("version", Func::from(version))?;
Ok(())
})
}
}