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}