use std::collections::HashSet;
use std::rc::Rc;
use crate::common::Function;
use crate::register_if_enabled;
use crate::{ArgumentType, Context, JmespathError, Rcvar, Runtime, Signature, Variable};
pub fn register(runtime: &mut Runtime) {
runtime.register_function("nanoid", Box::new(NanoidFn::new()));
runtime.register_function("ulid", Box::new(UlidFn::new()));
runtime.register_function("ulid_timestamp", Box::new(UlidTimestampFn::new()));
}
pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
register_if_enabled!(runtime, enabled, "nanoid", Box::new(NanoidFn::new()));
register_if_enabled!(runtime, enabled, "ulid", Box::new(UlidFn::new()));
register_if_enabled!(
runtime,
enabled,
"ulid_timestamp",
Box::new(UlidTimestampFn::new())
);
}
pub struct NanoidFn {
signature: Signature,
}
impl Default for NanoidFn {
fn default() -> Self {
Self::new()
}
}
impl NanoidFn {
pub fn new() -> Self {
Self {
signature: Signature::new(vec![], Some(ArgumentType::Number)),
}
}
}
impl Function for NanoidFn {
fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
self.signature.validate(args, ctx)?;
let id = if args.is_empty() {
nanoid::nanoid!()
} else {
let size = args[0].as_number().unwrap_or(21.0) as usize;
nanoid::nanoid!(size)
};
Ok(Rc::new(Variable::String(id)))
}
}
pub struct UlidFn {
signature: Signature,
}
impl Default for UlidFn {
fn default() -> Self {
Self::new()
}
}
impl UlidFn {
pub fn new() -> Self {
Self {
signature: Signature::new(vec![], None),
}
}
}
impl Function for UlidFn {
fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
self.signature.validate(args, ctx)?;
let id = ulid::Ulid::new().to_string();
Ok(Rc::new(Variable::String(id)))
}
}
pub struct UlidTimestampFn {
signature: Signature,
}
impl Default for UlidTimestampFn {
fn default() -> Self {
Self::new()
}
}
impl UlidTimestampFn {
pub fn new() -> Self {
Self {
signature: Signature::new(vec![ArgumentType::String], None),
}
}
}
impl Function for UlidTimestampFn {
fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
self.signature.validate(args, ctx)?;
let ulid_str = args[0].as_string().unwrap();
match ulid::Ulid::from_string(ulid_str) {
Ok(id) => {
let ts = id.timestamp_ms();
Ok(Rc::new(Variable::Number(
serde_json::Number::from_f64(ts as f64).unwrap(),
)))
}
Err(_) => Ok(Rc::new(Variable::Null)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup() -> Runtime {
let mut runtime = Runtime::new();
runtime.register_builtin_functions();
register(&mut runtime);
runtime
}
#[test]
fn test_nanoid_default() {
let runtime = setup();
let data = Variable::Null;
let expr = runtime.compile("nanoid()").unwrap();
let result = expr.search(&data).unwrap();
let id = result.as_string().unwrap();
assert_eq!(id.len(), 21);
}
#[test]
fn test_nanoid_custom_size() {
let runtime = setup();
let data = Variable::Null;
let expr = runtime.compile("nanoid(`10`)").unwrap();
let result = expr.search(&data).unwrap();
let id = result.as_string().unwrap();
assert_eq!(id.len(), 10);
}
#[test]
fn test_nanoid_unique() {
let runtime = setup();
let data = Variable::Null;
let expr = runtime.compile("nanoid()").unwrap();
let id1 = expr.search(&data).unwrap();
let id2 = expr.search(&data).unwrap();
assert_ne!(id1.as_string().unwrap(), id2.as_string().unwrap());
}
#[test]
fn test_ulid() {
let runtime = setup();
let data = Variable::Null;
let expr = runtime.compile("ulid()").unwrap();
let result = expr.search(&data).unwrap();
let id = result.as_string().unwrap();
assert_eq!(id.len(), 26);
}
#[test]
fn test_ulid_unique() {
let runtime = setup();
let data = Variable::Null;
let expr = runtime.compile("ulid()").unwrap();
let id1 = expr.search(&data).unwrap();
let id2 = expr.search(&data).unwrap();
assert_ne!(id1.as_string().unwrap(), id2.as_string().unwrap());
}
#[test]
fn test_ulid_timestamp() {
let runtime = setup();
let ulid = ulid::Ulid::new();
let ulid_str = ulid.to_string();
let expected_ts = ulid.timestamp_ms();
let data = Variable::String(ulid_str);
let expr = runtime.compile("ulid_timestamp(@)").unwrap();
let result = expr.search(&data).unwrap();
assert_eq!(result.as_number().unwrap(), expected_ts as f64);
}
#[test]
fn test_ulid_timestamp_invalid() {
let runtime = setup();
let data = Variable::from_json(r#""not-a-ulid""#).unwrap();
let expr = runtime.compile("ulid_timestamp(@)").unwrap();
let result = expr.search(&data).unwrap();
assert!(result.is_null());
}
#[test]
fn test_ulid_format() {
let runtime = setup();
let data = Variable::Null;
let expr = runtime.compile("ulid()").unwrap();
let result = expr.search(&data).unwrap();
let id = result.as_string().unwrap();
assert_eq!(id.len(), 26);
assert!(id.chars().all(|c| c.is_ascii_alphanumeric()));
}
}