Untyped and typed generation numbers.
Oxide’s Omicron tracks many different kinds of generation numbers: versions attached to individual resources, configuration generations, and so on. However, a bare integer does not carry information about the kind of counter it belongs to, which can lead to comparing or assigning the wrong generation number at runtime.
This crate provides a Generation type for untyped generation numbers,
along with a wrapper type around it that allows you to specify the kind of
counter a generation number belongs to.
This crate is modeled after newtype-uuid. Rust doesn’t have higher-kinded types, so one must write separate libraries for each kind of thing one might want to add sets of newtypes around.
use oxide_generation::{
GenericGeneration, TypedGeneration, TypedGenerationKind, TypedGenerationTag,
};
// First, define a type that represents the kind of generation number this is.
enum MyKind {}
impl TypedGenerationKind for MyKind {
// Tags are required to be ASCII identifiers, with underscores
// and dashes also supported. Because the tag is an associated
// constant, its validity is checked at compile time, at the point
// where the tag is first used.
const TAG: TypedGenerationTag = TypedGenerationTag::new("my_kind");
}
// Now, a generation number can be created with this kind.
let generation: TypedGeneration<MyKind> = "5".parse().unwrap();
// The Display (and therefore ToString) impls still show the same value.
assert_eq!(generation.to_string(), "5");
// The Debug impl will show the tag as well.
assert_eq!(format!("{:?}", generation), "5 (my_kind)");If you have a large number of generation kinds, consider using
oxide-generation-macros which comes with several convenience features.
use oxide_generation_macros::impl_typed_generation_kinds;
// Invoke this macro with:
impl_typed_generation_kinds! {
kinds = {
User = {},
Project = {},
// ...
},
}See oxide-generation-macros for more information.
For simpler cases, you can also write your own declarative macro. Use this template to get started:
macro_rules! impl_kinds {
($($kind:ident => $tag:literal),* $(,)?) => {
$(
pub enum $kind {}
impl TypedGenerationKind for $kind {
const TAG: TypedGenerationTag = TypedGenerationTag::new($tag);
}
)*
};
}
// Invoke this macro with:
impl_kinds! {
UserKind => "user",
ProjectKind => "project",
}In general, TypedGeneration uses the same wire and serialization formats as Generation.
This means that persistent representations of TypedGeneration are the same as
Generation; TypedGeneration is intended to be helpful within Rust code, not across
serialization boundaries.
- The
DisplayandFromStrimpls are forwarded to the underlyingGeneration. - If the
serdefeature is enabled,TypedGenerationwill serialize and deserialize using the same format asGeneration. - If the
schemars08feature is enabled,TypedGenerationwill implementJsonSchemaif the correspondingTypedGenerationKindimplementsJsonSchema.
To abstract over typed and untyped generation numbers, the GenericGeneration trait is
provided. This trait also permits conversions between typed and untyped generation numbers.
- This crate has no required dependencies. Optional features may add further dependencies.
default: Enables default features in the oxide-generation crate.std: Enables the use of the standard library, and thestdfeature of any enabled optional dependencies. Enabled by default.serde: Enables serialization and deserialization support via Serde. Not enabled by default.schemars08: Enables support for generating JSON schemas via schemars 0.8. Not enabled by default. Note that the format of the generated schema is not currently part of the stable API, though we hope to stabilize it in the future. Enabling this feature also enablesstd.proptest1: Enables support for generatingproptest::Arbitraryinstances of generation numbers. Not enabled by default. Enabling this feature also enablesstd.daft01: Enables diffing support viadaft0.1, treating generation numbers as leaf values. Not enabled by default.slog2: Enables logging support viaslog2.x, emitting generation numbers as integers. Not enabled by default.
The MSRV of this crate is Rust 1.85. In general, this crate will follow the MSRV of its dependencies, with an aim to be conservative.
Within the 0.x series, MSRV updates will be accompanied by a minor version bump.
This project is available under the terms of either the Apache 2.0 license or the MIT license.