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