Skip to main content

oxide_generation/
lib.rs

1//! Untyped and typed generation numbers.
2//!
3//! # Motivation
4//!
5//! Oxide's [Omicron](https://github.com/oxidecomputer/omicron) tracks many
6//! different kinds of generation numbers: versions attached to individual
7//! resources, configuration generations, and so on. However, a bare integer
8//! does not carry information about the kind of counter it belongs to, which
9//! can lead to comparing or assigning the wrong generation number at runtime.
10//!
11//! This crate provides a [`Generation`] type for untyped generation numbers,
12//! along with a wrapper type around it that allows you to specify the kind of
13//! counter a generation number belongs to.
14//!
15//! This crate is modeled after [newtype-uuid](https://docs.rs/newtype-uuid).
16//! Rust doesn't have [higher-kinded
17//! types](https://en.wikipedia.org/wiki/Kind_(type_theory)), so one must write
18//! separate libraries for each kind of thing one might want to add sets of
19//! newtypes around.
20//!
21//! # Example
22//!
23//! ```
24//! use oxide_generation::{
25//!     GenericGeneration, TypedGeneration, TypedGenerationKind, TypedGenerationTag,
26//! };
27//!
28//! // First, define a type that represents the kind of generation number this is.
29//! enum MyKind {}
30//!
31//! impl TypedGenerationKind for MyKind {
32//!     // Tags are required to be ASCII identifiers, with underscores
33//!     // and dashes also supported. Because the tag is an associated
34//!     // constant, its validity is checked at compile time, at the point
35//!     // where the tag is first used.
36//!     const TAG: TypedGenerationTag = TypedGenerationTag::new("my_kind");
37//! }
38//!
39//! // Now, a generation number can be created with this kind.
40//! let generation: TypedGeneration<MyKind> = "5".parse().unwrap();
41//!
42//! // The Display (and therefore ToString) impls still show the same value.
43//! assert_eq!(generation.to_string(), "5");
44//!
45//! // The Debug impl will show the tag as well.
46//! assert_eq!(format!("{:?}", generation), "5 (my_kind)");
47//! ```
48//!
49//! If you have a large number of generation kinds, consider using
50//! [`oxide-generation-macros`] which comes with several convenience features.
51//!
52//! ```
53//! use oxide_generation_macros::impl_typed_generation_kinds;
54//!
55//! // Invoke this macro with:
56//! impl_typed_generation_kinds! {
57//!     kinds = {
58//!         User = {},
59//!         Project = {},
60//!         // ...
61//!     },
62//! }
63//! ```
64//!
65//! See [`oxide-generation-macros`] for more information.
66//!
67//! [`oxide-generation-macros`]: https://docs.rs/oxide-generation-macros
68//!
69//! For simpler cases, you can also write your own declarative macro. Use this
70//! template to get started:
71//!
72//! ```rust
73//! # use oxide_generation::{TypedGenerationKind, TypedGenerationTag};
74//! macro_rules! impl_kinds {
75//!     ($($kind:ident => $tag:literal),* $(,)?) => {
76//!         $(
77//!             pub enum $kind {}
78//!
79//!             impl TypedGenerationKind for $kind {
80//!                 const TAG: TypedGenerationTag = TypedGenerationTag::new($tag);
81//!             }
82//!         )*
83//!     };
84//! }
85//!
86//! // Invoke this macro with:
87//! impl_kinds! {
88//!     UserKind => "user",
89//!     ProjectKind => "project",
90//! }
91//! ```
92//!
93//! # Implementations
94//!
95//! In general, [`TypedGeneration`] uses the same wire and serialization formats as [`Generation`].
96//! This means that persistent representations of [`TypedGeneration`] are the same as
97//! [`Generation`]; [`TypedGeneration`] is intended to be helpful within Rust code, not across
98//! serialization boundaries.
99//!
100//! - The `Display` and `FromStr` impls are forwarded to the underlying [`Generation`].
101//! - If the `serde` feature is enabled, `TypedGeneration` will serialize and deserialize using the
102//!   same format as [`Generation`].
103//! - If the `schemars08` feature is enabled, [`TypedGeneration`] will implement `JsonSchema` if the
104//!   corresponding [`TypedGenerationKind`] implements `JsonSchema`.
105//!
106//! To abstract over typed and untyped generation numbers, the [`GenericGeneration`] trait is
107//! provided. This trait also permits conversions between typed and untyped generation numbers.
108//!
109//! # Dependencies
110//!
111//! - This crate has no required dependencies. Optional features may add further dependencies.
112//!
113//! # Features
114//!
115//! - `default`: Enables default features in the oxide-generation crate.
116//! - `std`: Enables the use of the standard library, and the `std` feature of any enabled
117//!   optional dependencies. *Enabled by default.*
118//! - `serde`: Enables serialization and deserialization support via Serde. *Not enabled by
119//!   default.*
120//! - `schemars08`: Enables support for generating JSON schemas via schemars 0.8. *Not enabled by
121//!   default.* Note that the format of the generated schema is **not currently part** of the stable
122//!   API, though we hope to stabilize it in the future. Enabling this feature also enables `std`.
123//! - `proptest1`: Enables support for generating `proptest::Arbitrary` instances of generation
124//!   numbers. *Not enabled by default.* Enabling this feature also enables `std`.
125//! - `daft01`: Enables diffing support via [`daft`](https://docs.rs/daft) 0.1, treating generation
126//!   numbers as leaf values. *Not enabled by default.*
127//! - `slog2`: Enables logging support via [`slog`](https://docs.rs/slog) 2.x, emitting generation
128//!   numbers as integers. *Not enabled by default.*
129//!
130//! # Minimum supported Rust version (MSRV)
131//!
132//! The MSRV of this crate is **Rust 1.85.** In general, this crate will follow the MSRV of its
133//! dependencies, with an aim to be conservative.
134//!
135//! Within the 0.x series, MSRV updates will be accompanied by a minor version bump.
136
137#![forbid(unsafe_code)]
138#![warn(missing_docs)]
139#![cfg_attr(not(feature = "std"), no_std)]
140#![cfg_attr(doc_cfg, feature(doc_cfg))]
141
142/// Macro support for [`oxide-generation-macros`].
143///
144/// This module re-exports types needed for [`oxide-generation-macros`] to work.
145///
146/// [`oxide-generation-macros`]: https://docs.rs/oxide-generation-macros
147#[doc(hidden)]
148pub mod macro_support {
149    #[cfg(feature = "schemars08")]
150    pub use schemars as schemars08;
151    #[cfg(feature = "schemars08")]
152    pub use serde_json;
153}
154
155use core::{
156    cmp::Ordering,
157    fmt,
158    hash::{Hash, Hasher},
159    marker::PhantomData,
160    num::ParseIntError,
161    str::FromStr,
162};
163
164/// Generation numbers stored in the database, used for optimistic concurrency control
165///
166/// Generation numbers are monotonic counters: each successive version of a resource is assigned a
167/// generation number greater than the one before it.
168//
169// A generation is a value between 0 and 2**63-1, i.e. equivalent to a u63.
170// The reason is that generations are commonly stored as an i64 in databases,
171// and we want to disallow negative values. (We could potentially use two's
172// complement to store values greater than that as negative values, but surely
173// 2**63 is enough.)
174#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
175#[repr(transparent)]
176pub struct Generation(
177    // Generations are restricted to 2**63 - 1 as documented above.
178    u64,
179);
180
181impl Generation {
182    /// The generation number before any change has been applied, 0.
183    ///
184    /// Generation numbers conventionally start at 1 (see [`Self::new`]). Zero is
185    /// reserved to represent an initial or empty state that precedes the first
186    /// generation.
187    pub const ZERO: Generation = Generation(0);
188
189    /// The largest possible generation number, `2**63 - 1`.
190    //
191    // `as` is a little distasteful because it allows lossy conversion, but we
192    // know converting `i64::MAX` to `u64` will always succeed losslessly.
193    pub const MAX: Generation = Generation(i64::MAX as u64);
194
195    /// Creates the first generation number, which is 1.
196    ///
197    /// There is deliberately no `Default` impl: whether a value should start at
198    /// [`Self::ZERO`] or at the first generation is a choice callers must make
199    /// explicitly.
200    #[inline]
201    #[must_use]
202    #[allow(clippy::new_without_default)]
203    pub const fn new() -> Generation {
204        Generation(1)
205    }
206
207    /// Creates a generation number from a `u32`.
208    ///
209    /// Every `u32` is a valid generation number, so this conversion is infallible.
210    #[inline]
211    #[must_use]
212    pub const fn from_u32(value: u32) -> Generation {
213        // `as` is a little distasteful because it allows lossy conversion, but
214        // (a) we know converting `u32` to `u64` will always succeed
215        // losslessly, and (b) it allows to make this function `const`, unlike
216        // if we were to use `u64::from(value)`.
217        Generation(value as u64)
218    }
219
220    /// Returns the next generation number.
221    ///
222    /// # Panics
223    ///
224    /// Panics if the next generation number would exceed [`Generation::MAX`]. Use
225    /// [`Self::checked_next`] to handle overflow instead.
226    #[inline]
227    #[must_use]
228    pub const fn next(&self) -> Generation {
229        // It should technically be an operational error if this wraps or even
230        // exceeds the value allowed by an i64.  But it seems unlikely enough to
231        // happen in practice that we can probably feel safe with this.
232        let next_gen = self.0 + 1;
233        assert!(
234            next_gen <= Generation::MAX.0,
235            "attempt to overflow generation number"
236        );
237        Generation(next_gen)
238    }
239
240    /// Returns the next generation number, or `None` if it would exceed [`Generation::MAX`].
241    #[inline]
242    #[must_use]
243    pub const fn checked_next(&self) -> Option<Generation> {
244        let next_gen = self.0 + 1;
245        if next_gen <= Generation::MAX.0 {
246            Some(Generation(next_gen))
247        } else {
248            None
249        }
250    }
251
252    /// Returns the previous generation number, or `None` if this is the first generation.
253    ///
254    /// Both 1 and [`Self::ZERO`] are treated as having no predecessor: `prev` never
255    /// returns [`Self::ZERO`].
256    #[inline]
257    #[must_use]
258    pub const fn prev(&self) -> Option<Generation> {
259        if self.0 > 1 {
260            Some(Generation(self.0 - 1))
261        } else {
262            None
263        }
264    }
265
266    /// Returns the generation number as a `u64`.
267    #[inline]
268    pub const fn as_u64(self) -> u64 {
269        self.0
270    }
271
272    /// Returns the generation number as an `i64`.
273    ///
274    /// This conversion is infallible because generation numbers are restricted to `2**63 - 1`.
275    #[inline]
276    pub const fn as_i64(self) -> i64 {
277        // `as` is a little distasteful because it allows lossy conversion, but
278        // we know that generation numbers never exceed `i64::MAX`, so this
279        // conversion always succeeds losslessly.
280        self.0 as i64
281    }
282}
283
284// ---
285// Trait impls
286// ---
287
288impl fmt::Display for Generation {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        fmt::Display::fmt(&self.0, f)
291    }
292}
293
294impl FromStr for Generation {
295    type Err = ParseIntError;
296
297    fn from_str(s: &str) -> Result<Self, Self::Err> {
298        // Try to parse `s` as both an i64 and u64, returning the error from
299        // either.
300        let _ = i64::from_str(s)?;
301        Ok(Generation(u64::from_str(s)?))
302    }
303}
304
305impl From<u32> for Generation {
306    #[inline]
307    fn from(value: u32) -> Self {
308        Generation::from_u32(value)
309    }
310}
311
312impl From<Generation> for u64 {
313    #[inline]
314    fn from(g: Generation) -> Self {
315        g.0
316    }
317}
318
319impl From<Generation> for i64 {
320    #[inline]
321    fn from(g: Generation) -> Self {
322        g.as_i64()
323    }
324}
325
326impl From<&Generation> for i64 {
327    #[inline]
328    fn from(g: &Generation) -> Self {
329        // We have already validated that the value is within range.
330        g.as_i64()
331    }
332}
333
334impl TryFrom<u64> for Generation {
335    type Error = GenerationOverflowError;
336
337    fn try_from(value: u64) -> Result<Self, Self::Error> {
338        i64::try_from(value).map_err(|_| GenerationOverflowError(()))?;
339        Ok(Generation(value))
340    }
341}
342
343impl TryFrom<i64> for Generation {
344    type Error = GenerationNegativeError;
345
346    fn try_from(value: i64) -> Result<Self, Self::Error> {
347        Ok(Generation(
348            u64::try_from(value).map_err(|_| GenerationNegativeError(()))?,
349        ))
350    }
351}
352
353/// An error that occurred while converting a `u64` into a [`Generation`].
354///
355/// The value was greater than [`Generation::MAX`].
356#[derive(Clone, Debug)]
357#[non_exhaustive]
358pub struct GenerationOverflowError(());
359
360impl fmt::Display for GenerationOverflowError {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        f.write_str("generation number too large")
363    }
364}
365
366impl core::error::Error for GenerationOverflowError {}
367
368/// An error that occurred while converting an `i64` into a [`Generation`].
369///
370/// The value was negative.
371#[derive(Clone, Debug)]
372#[non_exhaustive]
373pub struct GenerationNegativeError(());
374
375impl fmt::Display for GenerationNegativeError {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        f.write_str("negative generation number")
378    }
379}
380
381impl core::error::Error for GenerationNegativeError {}
382
383/// A generation number with type-level information about what it's used for.
384///
385/// For more, see [the library documentation](crate).
386#[repr(transparent)]
387pub struct TypedGeneration<T: TypedGenerationKind> {
388    generation: Generation,
389    _phantom: PhantomData<T>,
390}
391
392impl<T: TypedGenerationKind> TypedGeneration<T> {
393    /// The generation number before any change has been applied, 0.
394    ///
395    /// Generation numbers conventionally start at 1 (see [`Self::new`]). Zero is
396    /// reserved to represent an initial or empty state that precedes the first
397    /// generation.
398    pub const ZERO: Self = Self {
399        generation: Generation::ZERO,
400        _phantom: PhantomData,
401    };
402
403    /// The largest possible generation number, `2**63 - 1`.
404    pub const MAX: Self = Self {
405        generation: Generation::MAX,
406        _phantom: PhantomData,
407    };
408
409    /// Creates the first generation number of this type, which is 1.
410    ///
411    /// There is deliberately no `Default` impl: whether a value should start at
412    /// [`Self::ZERO`] or at the first generation is a choice callers must make
413    /// explicitly.
414    #[inline]
415    #[must_use]
416    #[allow(clippy::new_without_default)]
417    pub const fn new() -> Self {
418        Self {
419            generation: Generation::new(),
420            _phantom: PhantomData,
421        }
422    }
423
424    /// Creates a generation number of this type from a `u32`.
425    ///
426    /// Every `u32` is a valid generation number, so this conversion is infallible.
427    #[inline]
428    #[must_use]
429    pub const fn from_u32(value: u32) -> Self {
430        Self {
431            generation: Generation::from_u32(value),
432            _phantom: PhantomData,
433        }
434    }
435
436    /// Returns the next generation number.
437    ///
438    /// # Panics
439    ///
440    /// Panics if the next generation number would exceed [`Self::MAX`]. Use
441    /// [`Self::checked_next`] to handle overflow instead.
442    #[inline]
443    #[must_use]
444    pub const fn next(&self) -> Self {
445        Self {
446            generation: self.generation.next(),
447            _phantom: PhantomData,
448        }
449    }
450
451    /// Returns the next generation number, or `None` if it would exceed [`Self::MAX`].
452    #[inline]
453    #[must_use]
454    pub const fn checked_next(&self) -> Option<Self> {
455        match self.generation.checked_next() {
456            Some(generation) => Some(Self {
457                generation,
458                _phantom: PhantomData,
459            }),
460            None => None,
461        }
462    }
463
464    /// Returns the previous generation number, or `None` if this is the first generation.
465    ///
466    /// Both 1 and [`Self::ZERO`] are treated as having no predecessor: `prev` never
467    /// returns [`Self::ZERO`].
468    #[inline]
469    #[must_use]
470    pub const fn prev(&self) -> Option<Self> {
471        match self.generation.prev() {
472            Some(generation) => Some(Self {
473                generation,
474                _phantom: PhantomData,
475            }),
476            None => None,
477        }
478    }
479
480    /// Returns the generation number as a `u64`.
481    #[inline]
482    pub const fn as_u64(self) -> u64 {
483        self.generation.as_u64()
484    }
485
486    /// Returns the generation number as an `i64`.
487    ///
488    /// This conversion is infallible because generation numbers are restricted to `2**63 - 1`.
489    #[inline]
490    pub const fn as_i64(self) -> i64 {
491        self.generation.as_i64()
492    }
493
494    /// Converts the generation number to one with looser semantics.
495    ///
496    /// By default, generation kinds are considered independent, and conversions
497    /// between them must happen via the [`GenericGeneration`] interface. But in
498    /// some cases, there may be a relationship between two different generation
499    /// kinds, and you may wish to easily convert generation numbers from one
500    /// kind to another.
501    ///
502    /// Typically, a conversion from `TypedGeneration<T>` to `TypedGeneration<U>`
503    /// is most useful when `T`'s semantics are a superset of `U`'s, or in other
504    /// words, when every `TypedGeneration<T>` is logically also a
505    /// `TypedGeneration<U>`.
506    ///
507    /// For instance:
508    ///
509    /// * Imagine you have [`TypedGenerationKind`]s for different types of
510    ///   database connections, where `DbConnKind` is the general type
511    ///   and `PgConnKind` is a specific kind for Postgres.
512    /// * Since every Postgres connection is also a database connection,
513    ///   a cast from `TypedGeneration<PgConnKind>` to `TypedGeneration<DbConnKind>`
514    ///   makes sense.
515    /// * The inverse cast would not make sense, as a database connection may not
516    ///   necessarily be a Postgres connection.
517    ///
518    /// This interface provides an alternative, safer way to perform this
519    /// conversion. Indicate your intention to allow a conversion between kinds
520    /// by implementing `From<T> for U`, as shown in the example below.
521    ///
522    /// # Examples
523    ///
524    /// ```
525    /// use oxide_generation::{TypedGeneration, TypedGenerationKind, TypedGenerationTag};
526    ///
527    /// // Let's say that these generation numbers track repositories for
528    /// // different version control systems, such that you have a generic
529    /// // RepoKind:
530    /// pub enum RepoKind {}
531    /// impl TypedGenerationKind for RepoKind {
532    ///     const TAG: TypedGenerationTag = TypedGenerationTag::new("repo");
533    /// }
534    ///
535    /// // You also have more specific kinds:
536    /// pub enum GitRepoKind {}
537    /// impl TypedGenerationKind for GitRepoKind {
538    ///     const TAG: TypedGenerationTag = TypedGenerationTag::new("git_repo");
539    /// }
540    /// // (and HgRepoKind, JujutsuRepoKind, etc...)
541    ///
542    /// // First, define a `From` impl. This impl indicates your desire
543    /// // to convert from one kind to another.
544    /// impl From<GitRepoKind> for RepoKind {
545    ///     fn from(value: GitRepoKind) -> Self {
546    ///         match value {}
547    ///     }
548    /// }
549    ///
550    /// // Now you can convert between them:
551    /// let git_generation: TypedGeneration<GitRepoKind> = TypedGeneration::from_u32(5);
552    /// let repo_generation: TypedGeneration<RepoKind> = git_generation.upcast();
553    /// ```
554    #[inline]
555    #[must_use]
556    pub const fn upcast<U: TypedGenerationKind>(self) -> TypedGeneration<U>
557    where
558        T: Into<U>,
559    {
560        TypedGeneration {
561            generation: self.generation,
562            _phantom: PhantomData,
563        }
564    }
565}
566
567// ---
568// Trait impls
569// ---
570
571impl<T: TypedGenerationKind> PartialEq for TypedGeneration<T> {
572    #[inline]
573    fn eq(&self, other: &Self) -> bool {
574        self.generation.eq(&other.generation)
575    }
576}
577
578impl<T: TypedGenerationKind> Eq for TypedGeneration<T> {}
579
580impl<T: TypedGenerationKind> PartialOrd for TypedGeneration<T> {
581    #[inline]
582    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
583        Some(self.cmp(other))
584    }
585}
586
587impl<T: TypedGenerationKind> Ord for TypedGeneration<T> {
588    #[inline]
589    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
590        self.generation.cmp(&other.generation)
591    }
592}
593
594impl<T: TypedGenerationKind> Hash for TypedGeneration<T> {
595    #[inline]
596    fn hash<H: Hasher>(&self, state: &mut H) {
597        self.generation.hash(state);
598    }
599}
600
601impl<T: TypedGenerationKind> fmt::Debug for TypedGeneration<T> {
602    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603        write!(f, "{} ({})", self.generation, T::TAG)
604    }
605}
606
607impl<T: TypedGenerationKind> fmt::Display for TypedGeneration<T> {
608    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609        self.generation.fmt(f)
610    }
611}
612
613impl<T: TypedGenerationKind> Clone for TypedGeneration<T> {
614    #[inline]
615    fn clone(&self) -> Self {
616        *self
617    }
618}
619
620impl<T: TypedGenerationKind> Copy for TypedGeneration<T> {}
621
622impl<T: TypedGenerationKind> FromStr for TypedGeneration<T> {
623    type Err = ParseError;
624
625    fn from_str(s: &str) -> Result<Self, Self::Err> {
626        let generation =
627            Generation::from_str(s).map_err(|error| ParseError { error, tag: T::TAG })?;
628        Ok(Self::from_untyped_generation(generation))
629    }
630}
631
632impl<T: TypedGenerationKind> From<u32> for TypedGeneration<T> {
633    #[inline]
634    fn from(value: u32) -> Self {
635        Self::from_u32(value)
636    }
637}
638
639impl<T: TypedGenerationKind> From<TypedGeneration<T>> for u64 {
640    #[inline]
641    fn from(g: TypedGeneration<T>) -> Self {
642        g.as_u64()
643    }
644}
645
646impl<T: TypedGenerationKind> From<TypedGeneration<T>> for i64 {
647    #[inline]
648    fn from(g: TypedGeneration<T>) -> Self {
649        g.as_i64()
650    }
651}
652
653impl<T: TypedGenerationKind> From<&TypedGeneration<T>> for i64 {
654    #[inline]
655    fn from(g: &TypedGeneration<T>) -> Self {
656        g.as_i64()
657    }
658}
659
660impl<T: TypedGenerationKind> TryFrom<u64> for TypedGeneration<T> {
661    type Error = GenerationOverflowError;
662
663    fn try_from(value: u64) -> Result<Self, Self::Error> {
664        Ok(Self::from_untyped_generation(Generation::try_from(value)?))
665    }
666}
667
668impl<T: TypedGenerationKind> TryFrom<i64> for TypedGeneration<T> {
669    type Error = GenerationNegativeError;
670
671    fn try_from(value: i64) -> Result<Self, Self::Error> {
672        Ok(Self::from_untyped_generation(Generation::try_from(value)?))
673    }
674}
675
676#[cfg(feature = "serde")]
677mod serde_imp {
678    use super::*;
679    use serde::{Deserialize, Deserializer, Serialize, Serializer};
680
681    impl<'de> Deserialize<'de> for Generation {
682        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
683        where
684            D: Deserializer<'de>,
685        {
686            let value = u64::deserialize(deserializer)?;
687            Generation::try_from(value).map_err(|GenerationOverflowError(_)| {
688                serde::de::Error::invalid_value(
689                    serde::de::Unexpected::Unsigned(value),
690                    &"an integer between 0 and 9223372036854775807",
691                )
692            })
693        }
694    }
695
696    // This is the equivalent of applying `#[serde(transparent)]`, written out by
697    // hand to match the manual `Deserialize` impl above.
698    impl Serialize for Generation {
699        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
700        where
701            S: Serializer,
702        {
703            self.0.serialize(serializer)
704        }
705    }
706
707    /// Deserializes a `TypedGeneration<T>` using the same format as [`Generation`].
708    ///
709    /// This impl does not require `T` to implement `Deserialize`.
710    impl<'de, T: TypedGenerationKind> Deserialize<'de> for TypedGeneration<T> {
711        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
712        where
713            D: Deserializer<'de>,
714        {
715            Ok(Self::from_untyped_generation(Generation::deserialize(
716                deserializer,
717            )?))
718        }
719    }
720
721    /// Serializes a `TypedGeneration<T>` using the same format as [`Generation`].
722    ///
723    /// This impl does not require `T` to implement `Serialize`.
724    impl<T: TypedGenerationKind> Serialize for TypedGeneration<T> {
725        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
726        where
727            S: Serializer,
728        {
729            self.generation.serialize(serializer)
730        }
731    }
732}
733
734#[cfg(feature = "schemars08")]
735mod schemars08_imp {
736    use super::*;
737    use schemars::{
738        JsonSchema, SchemaGenerator,
739        schema::{InstanceType, Metadata, NumberValidation, Schema, SchemaObject},
740        schema_for,
741    };
742
743    // This must be the first line of the doc comment on `Generation`.
744    const GENERATION_DESCRIPTION: &str =
745        "Generation numbers stored in the database, used for optimistic concurrency control";
746
747    const CRATE_NAME: &str = "oxide-generation";
748    // 0.x crates are semver-compatible only within a minor version, so this is
749    // "0.1" rather than "0".
750    const CRATE_VERSION: &str = "0.1";
751    const CRATE_PATH: &str = "oxide_generation::TypedGeneration";
752
753    /// Implements `JsonSchema` for `Generation`.
754    ///
755    /// The schema is that of a `u64` with a minimum of 0; the upper bound is not expressed in the
756    /// schema. The description matches the first line of the doc comment on [`Generation`].
757    impl JsonSchema for Generation {
758        #[inline]
759        fn schema_name() -> String {
760            "Generation".to_owned()
761        }
762
763        #[inline]
764        fn schema_id() -> std::borrow::Cow<'static, str> {
765            std::borrow::Cow::Borrowed("oxide_generation::Generation")
766        }
767
768        #[inline]
769        fn json_schema(_: &mut SchemaGenerator) -> Schema {
770            generation_schema_object().into()
771        }
772    }
773
774    /// Implements `JsonSchema` for `TypedGeneration<T>`, if `T` implements `JsonSchema`.
775    ///
776    /// * `schema_name` is set to `"TypedGenerationFor"`, concatenated by the schema name of `T`.
777    /// * `schema_id` is set to `format!("oxide_generation::TypedGeneration<{}>",
778    ///   T::schema_id())`.
779    /// * `json_schema` is the same as the one for `Generation` (including its description), with
780    ///   the `x-rust-type` extension to allow automatic replacement in typify and progenitor.
781    impl<T> JsonSchema for TypedGeneration<T>
782    where
783        T: TypedGenerationKind + JsonSchema,
784    {
785        #[inline]
786        fn schema_name() -> String {
787            // Use the alias if available, otherwise generate our own schema name.
788            if let Some(alias) = T::ALIAS {
789                alias.to_owned()
790            } else {
791                format!("TypedGenerationFor{}", T::schema_name())
792            }
793        }
794
795        #[inline]
796        fn schema_id() -> std::borrow::Cow<'static, str> {
797            std::borrow::Cow::Owned(format!(
798                "oxide_generation::TypedGeneration<{}>",
799                T::schema_id()
800            ))
801        }
802
803        #[inline]
804        fn json_schema(generator: &mut SchemaGenerator) -> Schema {
805            // Look at the schema for `T`. If it has `x-rust-type`, *and* if an
806            // alias is available, we can lift up the `x-rust-type` into our own schema.
807            //
808            // We use a new schema generator for `T` to avoid T's schema being
809            // added to the list of schemas in `generator` in case the lifting
810            // is successful.
811            let t_schema = schema_for!(T);
812            if let Some(schema) = lift_json_schema(&t_schema.schema, T::ALIAS) {
813                return schema.into();
814            }
815
816            SchemaObject {
817                extensions: [(
818                    "x-rust-type".to_string(),
819                    serde_json::json!({
820                        "crate": CRATE_NAME,
821                        "version": CRATE_VERSION,
822                        "path": CRATE_PATH,
823                        "parameters": [generator.subschema_for::<T>()]
824                    }),
825                )]
826                .into_iter()
827                .collect(),
828                ..generation_schema_object()
829            }
830            .into()
831        }
832    }
833
834    // The schema shared by `Generation` and `TypedGeneration<T>`: a `u64` with a
835    // minimum of 0, carrying the `Generation` description.
836    fn generation_schema_object() -> SchemaObject {
837        SchemaObject {
838            metadata: Some(Box::new(Metadata {
839                description: Some(GENERATION_DESCRIPTION.to_owned()),
840                ..Default::default()
841            })),
842            instance_type: Some(InstanceType::Integer.into()),
843            format: Some("uint64".to_string()),
844            number: Some(Box::new(NumberValidation {
845                minimum: Some(0.0),
846                ..Default::default()
847            })),
848            ..Default::default()
849        }
850    }
851
852    // ? on Option is too easy to make mistakes with, so we use `let Some(..) =
853    // .. else` instead.
854    #[allow(clippy::question_mark)]
855    fn lift_json_schema(schema: &SchemaObject, alias: Option<&str>) -> Option<SchemaObject> {
856        let Some(alias) = alias else {
857            return None;
858        };
859
860        let Some(v) = schema.extensions.get("x-rust-type") else {
861            return None;
862        };
863
864        // The crate, version and path must all be present.
865        let Some(crate_) = v.get("crate") else {
866            return None;
867        };
868        let Some(version) = v.get("version") else {
869            return None;
870        };
871        let Some(path) = v.get("path").and_then(|p| p.as_str()) else {
872            return None;
873        };
874        let Some((module_path, _)) = path.rsplit_once("::") else {
875            return None;
876        };
877
878        // The preconditions are all met. We can lift the schema by appending
879        // the alias to the module path.
880        let alias_path = format!("{module_path}::{alias}");
881
882        Some(SchemaObject {
883            extensions: [(
884                "x-rust-type".to_string(),
885                serde_json::json!({
886                    "crate": crate_,
887                    "version": version,
888                    "path": alias_path,
889                }),
890            )]
891            .into_iter()
892            .collect(),
893            ..generation_schema_object()
894        })
895    }
896}
897
898#[cfg(feature = "proptest1")]
899mod proptest1_imp {
900    use super::*;
901    use proptest::{
902        arbitrary::Arbitrary,
903        strategy::{BoxedStrategy, Strategy},
904    };
905
906    /// Parameters for use with `proptest` instances.
907    ///
908    /// This is currently not exported as a type because it has no options. But
909    /// it's left in as an extension point for the future.
910    #[derive(Clone, Debug, Default)]
911    pub struct GenerationParams(());
912
913    /// Generates random `Generation` instances.
914    ///
915    /// Values are drawn uniformly from the range `0..=`[`Generation::MAX`].
916    impl Arbitrary for Generation {
917        type Parameters = GenerationParams;
918        type Strategy = BoxedStrategy<Self>;
919
920        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
921            (0..=Generation::MAX.as_u64()).prop_map(Generation).boxed()
922        }
923    }
924
925    /// Parameters for use with `proptest` instances.
926    ///
927    /// This is currently not exported as a type because it has no options. But
928    /// it's left in as an extension point for the future.
929    #[derive(Clone, Debug, Default)]
930    pub struct TypedGenerationParams(());
931
932    /// Generates random `TypedGeneration<T>` instances.
933    ///
934    /// Values are drawn uniformly from the range `0..=`[`TypedGeneration::MAX`].
935    impl<T> Arbitrary for TypedGeneration<T>
936    where
937        T: TypedGenerationKind,
938    {
939        type Parameters = TypedGenerationParams;
940        type Strategy = BoxedStrategy<Self>;
941
942        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
943            (0..=Generation::MAX.as_u64())
944                .prop_map(|value| TypedGeneration::<T>::from_untyped_generation(Generation(value)))
945                .boxed()
946        }
947    }
948}
949
950#[cfg(feature = "daft01")]
951mod daft01_imp {
952    use super::*;
953
954    /// Diffs a `Generation` as a leaf value.
955    ///
956    /// Generation numbers are scalars, so diffing stops at them.
957    impl daft::Diffable for Generation {
958        type Diff<'daft> = daft::Leaf<&'daft Self>;
959
960        fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> {
961            daft::Leaf {
962                before: self,
963                after: other,
964            }
965        }
966    }
967
968    /// Diffs a `TypedGeneration<T>` as a leaf value.
969    ///
970    /// This impl does not require `T` to implement `Diffable`.
971    impl<T: TypedGenerationKind> daft::Diffable for TypedGeneration<T> {
972        type Diff<'daft> = daft::Leaf<&'daft Self>;
973
974        fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> {
975            daft::Leaf {
976                before: self,
977                after: other,
978            }
979        }
980    }
981}
982
983#[cfg(feature = "slog2")]
984mod slog2_imp {
985    use super::*;
986
987    /// Logs a `Generation` as an integer.
988    impl slog::Value for Generation {
989        fn serialize(
990            &self,
991            _rec: &slog::Record,
992            key: slog::Key,
993            serializer: &mut dyn slog::Serializer,
994        ) -> slog::Result {
995            serializer.emit_u64(key, self.as_u64())
996        }
997    }
998
999    /// Logs a `TypedGeneration<T>` as an integer, using the same format as [`Generation`].
1000    ///
1001    /// This impl does not require `T` to implement `slog::Value`.
1002    impl<T: TypedGenerationKind> slog::Value for TypedGeneration<T> {
1003        fn serialize(
1004            &self,
1005            _rec: &slog::Record,
1006            key: slog::Key,
1007            serializer: &mut dyn slog::Serializer,
1008        ) -> slog::Result {
1009            serializer.emit_u64(key, self.as_u64())
1010        }
1011    }
1012}
1013
1014/// Represents marker types that can be used as a type parameter for [`TypedGeneration`].
1015///
1016/// Generally, an implementation of this will be a zero-sized type that can never be constructed. An
1017/// empty struct or enum works well for this.
1018///
1019/// # Implementations
1020///
1021/// If the `schemars08` feature is enabled, and [`JsonSchema`] is implemented for a kind `T`, then
1022/// [`TypedGeneration`]`<T>` will also implement [`JsonSchema`].
1023///
1024/// If you have a large number of generation kinds, consider using
1025/// [`oxide-generation-macros`] which comes with several convenience features.
1026///
1027/// ```
1028/// use oxide_generation_macros::impl_typed_generation_kinds;
1029///
1030/// // Invoke this macro with:
1031/// impl_typed_generation_kinds! {
1032///     kinds = {
1033///         User = {},
1034///         Project = {},
1035///         // ...
1036///     },
1037/// }
1038/// ```
1039///
1040/// See [`oxide-generation-macros`] for more information.
1041///
1042/// [`oxide-generation-macros`]: https://docs.rs/oxide-generation-macros
1043/// [`JsonSchema`]: schemars::JsonSchema
1044pub trait TypedGenerationKind: Send + Sync + 'static {
1045    /// The corresponding tag for this kind.
1046    ///
1047    /// The tag forms a runtime representation of this type.
1048    ///
1049    /// The tag is required to be a static string.
1050    const TAG: TypedGenerationTag;
1051
1052    /// A string that corresponds to a type alias for `TypedGeneration<Self>`,
1053    /// if one is defined.
1054    ///
1055    /// The type alias must be defined in the same module as `Self`. This
1056    /// constant is used by the schemars integration to refer to embed a
1057    /// reference to that alias in the schema, if available.
1058    ///
1059    /// This is usually defined by the [`oxide-generation-macros`] crate.
1060    ///
1061    /// [`oxide-generation-macros`]: https://docs.rs/oxide-generation-macros
1062    const ALIAS: Option<&'static str> = None;
1063}
1064
1065/// Describes what kind of [`TypedGeneration`] something is.
1066///
1067/// This is the runtime equivalent of [`TypedGenerationKind`].
1068#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1069pub struct TypedGenerationTag(&'static str);
1070
1071impl TypedGenerationTag {
1072    /// Creates a new `TypedGenerationTag` from a static string.
1073    ///
1074    /// The string must be non-empty, and consist of:
1075    /// - ASCII letters
1076    /// - digits (only after the first character)
1077    /// - underscores
1078    /// - hyphens (only after the first character)
1079    ///
1080    /// # Panics
1081    ///
1082    /// Panics if the above conditions aren't met. Use [`Self::try_new`] to handle errors instead.
1083    #[must_use]
1084    pub const fn new(tag: &'static str) -> Self {
1085        match Self::try_new_impl(tag) {
1086            Ok(tag) => tag,
1087            Err(message) => panic!("{}", message),
1088        }
1089    }
1090
1091    /// Attempts to create a new `TypedGenerationTag` from a static string.
1092    ///
1093    /// The string must be non-empty, and consist of:
1094    /// - ASCII letters
1095    /// - digits (only after the first character)
1096    /// - underscores
1097    /// - hyphens (only after the first character)
1098    ///
1099    /// # Errors
1100    ///
1101    /// Returns a [`TagError`] if the above conditions aren't met.
1102    pub const fn try_new(tag: &'static str) -> Result<Self, TagError> {
1103        match Self::try_new_impl(tag) {
1104            Ok(tag) => Ok(tag),
1105            Err(message) => Err(TagError {
1106                input: tag,
1107                message,
1108            }),
1109        }
1110    }
1111
1112    const fn try_new_impl(tag: &'static str) -> Result<Self, &'static str> {
1113        if tag.is_empty() {
1114            return Err("tag must not be empty");
1115        }
1116
1117        let bytes = tag.as_bytes();
1118        if !(bytes[0].is_ascii_alphabetic() || bytes[0] == b'_') {
1119            return Err("first character of tag must be an ASCII letter or underscore");
1120        }
1121
1122        let mut bytes = match bytes {
1123            [_, rest @ ..] => rest,
1124            [] => panic!("already checked that it's non-empty"),
1125        };
1126        while let [rest @ .., last] = &bytes {
1127            if !(last.is_ascii_alphanumeric() || *last == b'_' || *last == b'-') {
1128                break;
1129            }
1130            bytes = rest;
1131        }
1132
1133        if !bytes.is_empty() {
1134            return Err("tag must only contain ASCII letters, digits, underscores, or hyphens");
1135        }
1136
1137        Ok(Self(tag))
1138    }
1139
1140    /// Returns the tag as a string.
1141    pub const fn as_str(&self) -> &'static str {
1142        self.0
1143    }
1144}
1145
1146impl fmt::Display for TypedGenerationTag {
1147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1148        f.write_str(self.0)
1149    }
1150}
1151
1152impl AsRef<str> for TypedGenerationTag {
1153    fn as_ref(&self) -> &str {
1154        self.0
1155    }
1156}
1157
1158/// An error that occurred while creating a [`TypedGenerationTag`].
1159#[derive(Clone, Debug)]
1160#[non_exhaustive]
1161pub struct TagError {
1162    /// The input string.
1163    pub input: &'static str,
1164
1165    /// The error message.
1166    pub message: &'static str,
1167}
1168
1169impl fmt::Display for TagError {
1170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1171        write!(
1172            f,
1173            "error creating tag from '{}': {}",
1174            self.input, self.message
1175        )
1176    }
1177}
1178
1179impl core::error::Error for TagError {}
1180
1181/// An error that occurred while parsing a [`TypedGeneration`].
1182#[derive(Clone, Debug)]
1183#[non_exhaustive]
1184pub struct ParseError {
1185    /// The underlying error.
1186    pub error: ParseIntError,
1187
1188    /// The tag of the generation number that failed to parse.
1189    pub tag: TypedGenerationTag,
1190}
1191
1192impl fmt::Display for ParseError {
1193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1194        write!(f, "error parsing generation number ({})", self.tag)
1195    }
1196}
1197
1198impl core::error::Error for ParseError {
1199    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
1200        Some(&self.error)
1201    }
1202}
1203
1204/// A trait abstracting over typed and untyped generation numbers.
1205///
1206/// This can be used to write code that's generic over [`TypedGeneration`], [`Generation`], and
1207/// other types that may wrap [`TypedGeneration`] (due to e.g. orphan rules).
1208///
1209/// This trait is similar to `From`, but a bit harder to get wrong -- in general, the conversion
1210/// from and to untyped generation numbers should be careful and explicit.
1211pub trait GenericGeneration {
1212    /// Creates a new instance of `Self` from an untyped [`Generation`].
1213    #[must_use]
1214    fn from_untyped_generation(generation: Generation) -> Self
1215    where
1216        Self: Sized;
1217
1218    /// Converts `self` into an untyped [`Generation`].
1219    #[must_use]
1220    fn into_untyped_generation(self) -> Generation
1221    where
1222        Self: Sized;
1223
1224    /// Returns the inner [`Generation`].
1225    ///
1226    /// Generally, [`into_untyped_generation`](Self::into_untyped_generation) should be preferred.
1227    /// However, in some cases it may be necessary to use this method to satisfy lifetime
1228    /// constraints.
1229    fn as_untyped_generation(&self) -> &Generation;
1230}
1231
1232impl GenericGeneration for Generation {
1233    #[inline]
1234    fn from_untyped_generation(generation: Generation) -> Self {
1235        generation
1236    }
1237
1238    #[inline]
1239    fn into_untyped_generation(self) -> Generation {
1240        self
1241    }
1242
1243    #[inline]
1244    fn as_untyped_generation(&self) -> &Generation {
1245        self
1246    }
1247}
1248
1249impl<T: TypedGenerationKind> GenericGeneration for TypedGeneration<T> {
1250    #[inline]
1251    fn from_untyped_generation(generation: Generation) -> Self {
1252        Self {
1253            generation,
1254            _phantom: PhantomData,
1255        }
1256    }
1257
1258    #[inline]
1259    fn into_untyped_generation(self) -> Generation {
1260        self.generation
1261    }
1262
1263    #[inline]
1264    fn as_untyped_generation(&self) -> &Generation {
1265        &self.generation
1266    }
1267}
1268
1269#[cfg(test)]
1270mod tests {
1271    use super::*;
1272
1273    enum MyKind {}
1274
1275    impl TypedGenerationKind for MyKind {
1276        const TAG: TypedGenerationTag = TypedGenerationTag::new("my_kind");
1277    }
1278
1279    #[test]
1280    fn test_validate_tags() {
1281        for &valid_tag in &[
1282            "a", "a-", "a_", "a-b", "a_b", "a1", "a1-", "a1_", "a1-b", "a1_b", "_a",
1283        ] {
1284            TypedGenerationTag::try_new(valid_tag).expect("tag is valid");
1285            // Should not panic
1286            _ = TypedGenerationTag::new(valid_tag);
1287        }
1288
1289        for invalid_tag in &["", "1", "-", "a1b!", "a1-b!", "a1_b:", "\u{1f4a9}"] {
1290            TypedGenerationTag::try_new(invalid_tag).unwrap_err();
1291        }
1292    }
1293
1294    // This test just ensures that `GenericGeneration` is object-safe.
1295    #[test]
1296    #[cfg(feature = "std")]
1297    fn test_generic_generation_object_safe() {
1298        let generation = Generation::new();
1299        let box_generation = Box::new(generation) as Box<dyn GenericGeneration>;
1300        assert_eq!(box_generation.as_untyped_generation(), &generation);
1301    }
1302
1303    #[test]
1304    fn test_next_and_prev() {
1305        // The first generation is 1, and it has no predecessor.
1306        let first = Generation::new();
1307        assert_eq!(first.as_u64(), 1);
1308        assert_eq!(first.prev(), None);
1309        assert_eq!(first.next(), Generation::from_u32(2));
1310        assert_eq!(first.checked_next(), Some(Generation::from_u32(2)));
1311
1312        // Generation 0 also has no predecessor.
1313        let zero = Generation::ZERO;
1314        assert_eq!(zero.as_u64(), 0);
1315        assert_eq!(zero.prev(), None);
1316        assert_eq!(zero.next(), first);
1317
1318        // One below the maximum still has a successor.
1319        let almost_max = Generation::try_from(Generation::MAX.as_u64() - 1).unwrap();
1320        assert_eq!(almost_max.next(), Generation::MAX);
1321        assert_eq!(almost_max.checked_next(), Some(Generation::MAX));
1322        assert_eq!(almost_max.prev().unwrap().as_u64(), i64::MAX as u64 - 2);
1323
1324        // The maximum has no successor.
1325        assert_eq!(Generation::MAX.checked_next(), None);
1326        assert_eq!(Generation::MAX.prev(), Some(almost_max));
1327
1328        // The same holds for typed generation numbers.
1329        let typed_max = TypedGeneration::<MyKind>::MAX;
1330        assert_eq!(typed_max.as_u64(), Generation::MAX.as_u64());
1331        assert_eq!(typed_max.checked_next(), None);
1332        assert_eq!(
1333            typed_max.prev().map(|g| g.as_u64()),
1334            Some(Generation::MAX.as_u64() - 1)
1335        );
1336        assert_eq!(TypedGeneration::<MyKind>::new().as_u64(), 1);
1337        assert_eq!(TypedGeneration::<MyKind>::ZERO.as_u64(), 0);
1338        assert_eq!(TypedGeneration::<MyKind>::ZERO.next().as_u64(), 1);
1339    }
1340
1341    #[test]
1342    #[should_panic(expected = "attempt to overflow generation number")]
1343    fn test_next_at_max_panics() {
1344        _ = Generation::MAX.next();
1345    }
1346
1347    #[test]
1348    #[should_panic(expected = "attempt to overflow generation number")]
1349    fn test_typed_next_at_max_panics() {
1350        _ = TypedGeneration::<MyKind>::MAX.next();
1351    }
1352
1353    #[test]
1354    fn test_try_from() {
1355        // Zero and the maximum are accepted, but one past the maximum is not.
1356        assert_eq!(Generation::try_from(0_u64).unwrap(), Generation::ZERO);
1357        assert_eq!(
1358            Generation::try_from(i64::MAX as u64).unwrap(),
1359            Generation::MAX
1360        );
1361        Generation::try_from(i64::MAX as u64 + 1).unwrap_err();
1362        Generation::try_from(u64::MAX).unwrap_err();
1363
1364        // Non-negative i64s are accepted, negative ones are not.
1365        assert_eq!(Generation::try_from(0_i64).unwrap().as_u64(), 0);
1366        assert_eq!(Generation::try_from(i64::MAX).unwrap(), Generation::MAX);
1367        Generation::try_from(-1_i64).unwrap_err();
1368        Generation::try_from(i64::MIN).unwrap_err();
1369
1370        // The same holds for typed generation numbers.
1371        assert_eq!(
1372            TypedGeneration::<MyKind>::try_from(i64::MAX as u64).unwrap(),
1373            TypedGeneration::<MyKind>::MAX
1374        );
1375        TypedGeneration::<MyKind>::try_from(i64::MAX as u64 + 1).unwrap_err();
1376        TypedGeneration::<MyKind>::try_from(-1_i64).unwrap_err();
1377    }
1378
1379    #[test]
1380    fn test_conversions() {
1381        let generation = Generation::from_u32(5);
1382        assert_eq!(u64::from(generation), 5);
1383        assert_eq!(i64::from(generation), 5);
1384        assert_eq!(i64::from(&generation), 5);
1385        assert_eq!(Generation::from(5_u32), generation);
1386
1387        let typed = TypedGeneration::<MyKind>::from_u32(5);
1388        assert_eq!(u64::from(typed), 5);
1389        assert_eq!(i64::from(typed), 5);
1390        assert_eq!(i64::from(&typed), 5);
1391        assert_eq!(TypedGeneration::<MyKind>::from(5_u32), typed);
1392        assert_eq!(typed.into_untyped_generation(), generation);
1393        assert_eq!(typed.as_untyped_generation(), &generation);
1394        assert_eq!(typed.as_i64(), 5);
1395    }
1396
1397    #[test]
1398    fn test_from_str() {
1399        assert_eq!(Generation::from_str("0").unwrap().as_u64(), 0);
1400        assert_eq!(Generation::from_str("1").unwrap(), Generation::new());
1401        assert_eq!(
1402            Generation::from_str("9223372036854775807").unwrap(),
1403            Generation::MAX
1404        );
1405
1406        // Negative numbers and numbers greater than the maximum are both
1407        // rejected.
1408        Generation::from_str("-1").unwrap_err();
1409        Generation::from_str("9223372036854775808").unwrap_err();
1410
1411        // The same holds for typed generation numbers.
1412        assert_eq!(
1413            "9223372036854775807"
1414                .parse::<TypedGeneration<MyKind>>()
1415                .unwrap(),
1416            TypedGeneration::<MyKind>::MAX
1417        );
1418        "-1".parse::<TypedGeneration<MyKind>>().unwrap_err();
1419        "9223372036854775808"
1420            .parse::<TypedGeneration<MyKind>>()
1421            .unwrap_err();
1422    }
1423
1424    // The remaining tests format values, which requires an allocator.
1425
1426    #[test]
1427    #[cfg(feature = "std")]
1428    fn test_display_and_debug() {
1429        assert_eq!(Generation::new().to_string(), "1");
1430
1431        let generation = Generation::from_u32(5);
1432        assert_eq!(generation.to_string(), "5");
1433
1434        let typed = TypedGeneration::<MyKind>::from_u32(5);
1435        assert_eq!(typed.to_string(), "5");
1436        assert_eq!(format!("{typed:?}"), "5 (my_kind)");
1437    }
1438
1439    #[test]
1440    #[cfg(feature = "std")]
1441    fn test_error_displays() {
1442        assert_eq!(
1443            Generation::try_from(i64::MAX as u64 + 1)
1444                .unwrap_err()
1445                .to_string(),
1446            "generation number too large"
1447        );
1448        assert_eq!(
1449            Generation::try_from(-1_i64).unwrap_err().to_string(),
1450            "negative generation number"
1451        );
1452        assert_eq!(
1453            "-1".parse::<TypedGeneration<MyKind>>()
1454                .unwrap_err()
1455                .to_string(),
1456            "error parsing generation number (my_kind)"
1457        );
1458    }
1459
1460    #[test]
1461    #[cfg(all(feature = "serde", feature = "std"))]
1462    fn test_serde() {
1463        let generation = Generation::from_u32(5);
1464        assert_eq!(serde_json::to_string(&generation).unwrap(), "5");
1465        assert_eq!(serde_json::from_str::<Generation>("5").unwrap(), generation);
1466
1467        // The first generation round-trips as the bare integer 1.
1468        assert_eq!(serde_json::to_string(&Generation::new()).unwrap(), "1");
1469        assert_eq!(
1470            serde_json::from_str::<Generation>("1").unwrap(),
1471            Generation::new()
1472        );
1473
1474        // Both ends of the valid range deserialize, and values outside it --
1475        // whether above the maximum or negative -- do not.
1476        assert_eq!(
1477            serde_json::from_str::<Generation>("0").unwrap(),
1478            Generation::ZERO
1479        );
1480        assert_eq!(
1481            serde_json::from_str::<Generation>(&Generation::MAX.as_u64().to_string()).unwrap(),
1482            Generation::MAX
1483        );
1484        for bad_value in [Generation::MAX.as_u64() + 1, u64::MAX] {
1485            serde_json::from_str::<Generation>(&bad_value.to_string()).unwrap_err();
1486        }
1487        for bad_value in [-1_i64, i64::MIN] {
1488            serde_json::from_str::<Generation>(&bad_value.to_string()).unwrap_err();
1489        }
1490
1491        let typed = TypedGeneration::<MyKind>::from_u32(5);
1492        assert_eq!(serde_json::to_string(&typed).unwrap(), "5");
1493        assert_eq!(
1494            serde_json::from_str::<TypedGeneration<MyKind>>("5").unwrap(),
1495            typed
1496        );
1497
1498        // One past the maximum is rejected with a message that describes the
1499        // valid range.
1500        let error = serde_json::from_str::<Generation>("9223372036854775808").unwrap_err();
1501        assert_eq!(
1502            error.to_string(),
1503            "invalid value: integer `9223372036854775808`, \
1504             expected an integer between 0 and 9223372036854775807"
1505        );
1506        serde_json::from_str::<TypedGeneration<MyKind>>("9223372036854775808").unwrap_err();
1507    }
1508
1509    #[test]
1510    #[cfg(feature = "daft01")]
1511    fn test_daft() {
1512        use daft::Diffable;
1513
1514        let before = Generation::from_u32(5);
1515        let after = Generation::from_u32(6);
1516        let diff = before.diff(&after);
1517        assert_eq!(diff.before, &before);
1518        assert_eq!(diff.after, &after);
1519
1520        // The same holds for typed generation numbers.
1521        let typed_before = TypedGeneration::<MyKind>::from_u32(5);
1522        let typed_after = TypedGeneration::<MyKind>::from_u32(6);
1523        let typed_diff = typed_before.diff(&typed_after);
1524        assert_eq!(typed_diff.before, &typed_before);
1525        assert_eq!(typed_diff.after, &typed_after);
1526    }
1527
1528    #[test]
1529    #[cfg(feature = "slog2")]
1530    fn test_slog() {
1531        // Records the value passed to `emit_u64`, so that the test can check
1532        // that generation numbers are logged as integers rather than strings.
1533        struct RecordingSerializer(Option<u64>);
1534
1535        impl slog::Serializer for RecordingSerializer {
1536            fn emit_u64(&mut self, _key: slog::Key, value: u64) -> slog::Result {
1537                self.0 = Some(value);
1538                Ok(())
1539            }
1540
1541            fn emit_arguments(&mut self, _key: slog::Key, _value: &fmt::Arguments) -> slog::Result {
1542                panic!("generation numbers are emitted via emit_u64")
1543            }
1544        }
1545
1546        // The record is built inline because `format_args!` produces a
1547        // temporary that only lives until the end of the statement.
1548        //
1549        // slog's `Key` is a plain `&'static str` unless the `dynamic-keys`
1550        // feature is enabled, so the conversion below is sometimes a no-op.
1551        #[allow(clippy::useless_conversion)]
1552        fn emitted_u64(value: &dyn slog::Value) -> Option<u64> {
1553            let mut serializer = RecordingSerializer(None);
1554            value
1555                .serialize(
1556                    &slog::record!(
1557                        slog::Level::Info,
1558                        "",
1559                        &format_args!(""),
1560                        slog::BorrowedKV(&())
1561                    ),
1562                    "generation".into(),
1563                    &mut serializer,
1564                )
1565                .expect("generation number was serialized");
1566            serializer.0
1567        }
1568
1569        assert_eq!(emitted_u64(&Generation::from_u32(5)), Some(5));
1570
1571        // The same holds for typed generation numbers.
1572        assert_eq!(
1573            emitted_u64(&TypedGeneration::<MyKind>::from_u32(6)),
1574            Some(6)
1575        );
1576    }
1577
1578    #[test]
1579    #[cfg(feature = "schemars08")]
1580    fn test_generation_schema() {
1581        // This schema must match the one omicron generates today, so that wire
1582        // schemas are unchanged.
1583        let schema = <Generation as schemars::JsonSchema>::json_schema(
1584            &mut schemars::SchemaGenerator::default(),
1585        );
1586        assert_eq!(
1587            serde_json::to_value(&schema).unwrap(),
1588            serde_json::json!({
1589                "description": "Generation numbers stored in the database, \
1590                                used for optimistic concurrency control",
1591                "type": "integer",
1592                "format": "uint64",
1593                "minimum": 0.0,
1594            })
1595        );
1596    }
1597}