rqjs_ext/modules/
uuid.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use 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    // module_builder::ModuleInfo,
15    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// impl From<UuidModule> for ModuleInfo<UuidModule> {
142//     fn from(val: UuidModule) -> Self {
143//         ModuleInfo {
144//             name: "uuid",
145//             module: val,
146//         }
147//     }
148// }