1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! # SCRU64: Sortable, Clock-based, Realm-specifically Unique identifier
//!
//! SCRU64 ID offers compact, time-ordered unique identifiers generated by
//! distributed nodes. SCRU64 has the following features:
//!
//! - ~62-bit non-negative integer storable as signed/unsigned 64-bit integer
//! - Sortable by generation time (as integer and as text)
//! - 12-digit case-insensitive textual representation (Base36)
//! - ~38-bit Unix epoch-based timestamp that ensures useful life until year 4261
//! - Variable-length node/machine ID and counter fields that share 24 bits
//!
//! ```rust
//! // pass node ID through environment variable
//! std::env::set_var("SCRU64_NODE_SPEC", "42/8");
//!
//! // generate a new identifier object
//! let x = scru64::new();
//! println!("{}", x); // e.g., "0u2r85hm2pt3"
//! println!("{}", x.to_u64()); // as a 64-bit unsigned integer
//!
//! // generate a textual representation directly
//! println!("{}", scru64::new_string()); // e.g., "0u2r85hm2pt4"
//! ```
//!
//! See [SCRU64 Specification] for details.
//!
//! SCRU64's uniqueness is realm-specific, i.e., dependent on the centralized
//! assignment of node ID to each generator. If you need decentralized, globally
//! unique time-ordered identifiers, consider [SCRU128].
//!
//! [SCRU64 Specification]: https://github.com/scru64/spec
//! [SCRU128]: https://github.com/scru128/spec
//!
//! ## Crate features
//!
//! Default features:
//!
//! - `std` integrates the library with, among others, the system clock to draw
//!   current timestamps. Without `std`, this crate provides limited functionality
//!   available under `no_std` environments.
//! - `global_gen` (implies `std`) enables the primary [`new()`] and [`new_string()`]
//!   functions and the process-wide global generator under the hood.
//!
//! Optional features:
//!
//! - `serde` enables serialization/deserialization via serde.
//! - `tokio` (together with `global_gen`) enables the [`async_tokio::new()`] and
//!   [`async_tokio::new_string()`] functions, the non-blocking counterpart of [`new()`]
//!   and [`new_string()`].

#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]

pub mod generator;

mod identifier;
pub use identifier::{ParseError, PartsError, RangeError, Scru64Id};

#[cfg(feature = "global_gen")]
pub use shortcut::{new, new_string};

#[cfg(all(feature = "global_gen", feature = "tokio"))]
pub use shortcut::async_tokio;

#[cfg(test)]
mod test_cases;

/// The total size in bits of the `node_id` and `counter` fields.
const NODE_CTR_SIZE: u8 = 24;

#[cfg(feature = "global_gen")]
#[cfg_attr(docsrs, doc(cfg(feature = "global_gen")))]
mod shortcut {
    use std::{thread, time};

    use crate::{generator::GlobalGenerator, Scru64Id};

    const DELAY: time::Duration = time::Duration::from_millis(64);

    /// Generates a new SCRU64 ID object using the global generator.
    ///
    /// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
    /// environment variable by default, and it panics if it fails to read a well-formed node spec
    /// string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method is first
    /// called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec string format.
    ///
    /// This function usually returns a value immediately, but if not possible, it sleeps and waits
    /// for the next timestamp tick. It employs blocking sleep to wait; see [`async_tokio::new`]
    /// for the non-blocking equivalent.
    ///
    /// # Panics
    ///
    /// Panics if the global generator is not properly configured.
    pub fn new() -> Scru64Id {
        loop {
            if let Some(value) = GlobalGenerator.generate() {
                break value;
            } else {
                thread::sleep(DELAY);
            }
        }
    }

    /// Generates a new SCRU64 ID encoded in the 12-digit canonical string representation using the
    /// global generator.
    ///
    /// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
    /// environment variable by default, and it panics if it fails to read a well-formed node spec
    /// string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method is first
    /// called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec string format.
    ///
    /// This function usually returns a value immediately, but if not possible, it sleeps and waits
    /// for the next timestamp tick. It employs blocking sleep to wait; see
    /// [`async_tokio::new_string`] for the non-blocking equivalent.
    ///
    /// # Panics
    ///
    /// Panics if the global generator is not properly configured.
    pub fn new_string() -> String {
        new().into()
    }

    /// Generates 100k monotonically increasing IDs.
    #[cfg(test)]
    #[test]
    fn test() {
        std::env::set_var("SCRU64_NODE_SPEC", "42/8");

        let mut prev = new_string();
        for _ in 0..100_000 {
            let curr = new_string();
            assert!(prev < curr);
            prev = curr;
        }
    }

    /// Non-blocking global generator functions using `tokio`.
    #[cfg(feature = "tokio")]
    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
    pub mod async_tokio {
        use super::{GlobalGenerator, Scru64Id, DELAY};

        /// Generates a new SCRU64 ID object using the global generator.
        ///
        /// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
        /// environment variable by default, and it panics if it fails to read a well-formed node
        /// spec string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method
        /// is first called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec
        /// string format.
        ///
        /// This function usually returns a value immediately, but if not possible, it sleeps and
        /// waits for the next timestamp tick.
        ///
        /// # Panics
        ///
        /// Panics if the global generator is not properly configured.
        pub async fn new() -> Scru64Id {
            loop {
                if let Some(value) = GlobalGenerator.generate() {
                    break value;
                } else {
                    tokio::time::sleep(DELAY).await;
                }
            }
        }

        /// Generates a new SCRU64 ID encoded in the 12-digit canonical string representation using
        /// the global generator.
        ///
        /// The [`GlobalGenerator`] reads the node configuration from the `SCRU64_NODE_SPEC`
        /// environment variable by default, and it panics if it fails to read a well-formed node
        /// spec string (e.g., `"42/8"`, `"0xb00/12"`, `"0u2r85hm2pt3/16"`) when a generator method
        /// is first called. See also [`NodeSpec`](crate::generator::NodeSpec) for the node spec
        /// string format.
        ///
        /// This function usually returns a value immediately, but if not possible, it sleeps and
        /// waits for the next timestamp tick.
        ///
        /// # Panics
        ///
        /// Panics if the global generator is not properly configured.
        pub async fn new_string() -> String {
            loop {
                if let Some(value) = GlobalGenerator.generate() {
                    break value.into();
                } else {
                    tokio::time::sleep(DELAY).await;
                }
            }
        }

        /// Generates 100k monotonically increasing IDs.
        #[cfg(test)]
        #[tokio::test]
        async fn test() {
            std::env::set_var("SCRU64_NODE_SPEC", "42/8");

            let mut prev = new_string().await;
            for _ in 0..100_000 {
                let curr = new_string().await;
                assert!(prev < curr);
                prev = curr;
            }
        }
    }
}