vrl/stdlib/
uuid_v7.rs

1use crate::compiler::prelude::*;
2use bytes::Bytes;
3use chrono::{DateTime, Utc};
4use std::sync::LazyLock;
5use uuid::{NoContext, timestamp::Timestamp};
6
7static DEFAULT_TIMESTAMP: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("`now()`")));
8
9static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
10    vec![
11        Parameter::optional(
12            "timestamp",
13            kind::TIMESTAMP,
14            "The timestamp used to generate the UUIDv7.",
15        )
16        .default(&DEFAULT_TIMESTAMP),
17    ]
18});
19
20#[allow(clippy::cast_sign_loss)] // TODO consider removal options
21fn uuid_v7(timestamp: Option<Value>) -> Resolved {
22    let utc_timestamp: DateTime<Utc> = if let Some(timestamp) = timestamp {
23        timestamp.try_timestamp()?
24    } else {
25        Utc::now()
26    };
27
28    let seconds = utc_timestamp.timestamp() as u64;
29    let nanoseconds = match utc_timestamp.timestamp_nanos_opt() {
30        #[allow(clippy::cast_possible_truncation)] //TODO evaluate removal options
31        Some(nanos) => nanos as u32,
32        None => return Err(ValueError::OutOfRange(Kind::timestamp()).into()),
33    };
34    let timestamp = Timestamp::from_unix(NoContext, seconds, nanoseconds);
35
36    let mut buffer = [0; 36];
37    let uuid = uuid::Uuid::new_v7(timestamp)
38        .hyphenated()
39        .encode_lower(&mut buffer);
40    Ok(Bytes::copy_from_slice(uuid.as_bytes()).into())
41}
42
43#[derive(Clone, Copy, Debug)]
44pub struct UuidV7;
45
46impl Function for UuidV7 {
47    fn identifier(&self) -> &'static str {
48        "uuid_v7"
49    }
50
51    fn usage(&self) -> &'static str {
52        "Generates a random [UUIDv7](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#name-uuid-version-7) string."
53    }
54
55    fn category(&self) -> &'static str {
56        Category::Random.as_ref()
57    }
58
59    fn return_kind(&self) -> u16 {
60        kind::BYTES
61    }
62
63    fn parameters(&self) -> &'static [Parameter] {
64        PARAMETERS.as_slice()
65    }
66
67    fn examples(&self) -> &'static [Example] {
68        &[
69            example! {
70                title: "Create a UUIDv7 with implicit `now()`",
71                source: "uuid_v7()",
72                result: Ok("0135ddb4-a444-794c-a7a2-088f260104c0"),
73                deterministic: false,
74            },
75            example! {
76                title: "Create a UUIDv7 with explicit `now()`",
77                source: "uuid_v7(now())",
78                result: Ok("0135ddb4-a444-794c-a7a2-088f260104c0"),
79                deterministic: false,
80            },
81            example! {
82                title: "Create a UUIDv7 with custom timestamp",
83                source: "uuid_v7(t'2020-12-30T22:20:53.824727Z')",
84                result: Ok("0176b5bd-5d19-794c-a7a2-088f260104c0"),
85                deterministic: false,
86            },
87        ]
88    }
89
90    fn compile(
91        &self,
92        _state: &state::TypeState,
93        _ctx: &mut FunctionCompileContext,
94        arguments: ArgumentList,
95    ) -> Compiled {
96        let timestamp = arguments.optional("timestamp");
97
98        Ok(UuidV7Fn { timestamp }.as_expr())
99    }
100}
101
102#[derive(Debug, Clone)]
103struct UuidV7Fn {
104    timestamp: Option<Box<dyn Expression>>,
105}
106
107impl FunctionExpression for UuidV7Fn {
108    fn resolve(&self, ctx: &mut Context) -> Resolved {
109        let timestamp = self
110            .timestamp
111            .as_ref()
112            .map(|m| m.resolve(ctx))
113            .transpose()?;
114
115        uuid_v7(timestamp)
116    }
117
118    fn type_def(&self, _: &TypeState) -> TypeDef {
119        TypeDef::bytes().infallible()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use crate::value::Value;
127    use std::collections::BTreeMap;
128
129    test_type_def![default {
130        expr: |_| { UuidV7Fn { timestamp: None } },
131        want: TypeDef::bytes().infallible(),
132    }];
133
134    #[test]
135    fn uuid_v7() {
136        let mut state = state::RuntimeState::default();
137        let mut object: Value = Value::Object(BTreeMap::new());
138        let tz = TimeZone::default();
139        let mut ctx = Context::new(&mut object, &mut state, &tz);
140        let value = UuidV7Fn { timestamp: None }.resolve(&mut ctx).unwrap();
141
142        assert!(matches!(&value, Value::Bytes(_)));
143
144        match value {
145            Value::Bytes(val) => {
146                let val = String::from_utf8_lossy(&val);
147                uuid::Uuid::parse_str(&val).expect("valid UUID V7");
148            }
149            _ => unreachable!(),
150        }
151    }
152}