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