#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(doc_cfg, feature(doc_cfg))]
#[doc(hidden)]
pub mod macro_support {
#[cfg(feature = "schemars08")]
pub use schemars as schemars08;
#[cfg(feature = "schemars08")]
pub use serde_json;
}
use core::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
num::ParseIntError,
str::FromStr,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Generation(
u64,
);
impl Generation {
pub const ZERO: Generation = Generation(0);
pub const MAX: Generation = Generation(i64::MAX as u64);
#[inline]
#[must_use]
#[allow(clippy::new_without_default)]
pub const fn new() -> Generation {
Generation(1)
}
#[inline]
#[must_use]
pub const fn from_u32(value: u32) -> Generation {
Generation(value as u64)
}
#[inline]
#[must_use]
pub const fn next(&self) -> Generation {
let next_gen = self.0 + 1;
assert!(
next_gen <= Generation::MAX.0,
"attempt to overflow generation number"
);
Generation(next_gen)
}
#[inline]
#[must_use]
pub const fn checked_next(&self) -> Option<Generation> {
let next_gen = self.0 + 1;
if next_gen <= Generation::MAX.0 {
Some(Generation(next_gen))
} else {
None
}
}
#[inline]
#[must_use]
pub const fn prev(&self) -> Option<Generation> {
if self.0 > 1 {
Some(Generation(self.0 - 1))
} else {
None
}
}
#[inline]
pub const fn as_u64(self) -> u64 {
self.0
}
#[inline]
pub const fn as_i64(self) -> i64 {
self.0 as i64
}
}
impl fmt::Display for Generation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for Generation {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let _ = i64::from_str(s)?;
Ok(Generation(u64::from_str(s)?))
}
}
impl From<u32> for Generation {
#[inline]
fn from(value: u32) -> Self {
Generation::from_u32(value)
}
}
impl From<Generation> for u64 {
#[inline]
fn from(g: Generation) -> Self {
g.0
}
}
impl From<Generation> for i64 {
#[inline]
fn from(g: Generation) -> Self {
g.as_i64()
}
}
impl From<&Generation> for i64 {
#[inline]
fn from(g: &Generation) -> Self {
g.as_i64()
}
}
impl TryFrom<u64> for Generation {
type Error = GenerationOverflowError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
i64::try_from(value).map_err(|_| GenerationOverflowError(()))?;
Ok(Generation(value))
}
}
impl TryFrom<i64> for Generation {
type Error = GenerationNegativeError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Ok(Generation(
u64::try_from(value).map_err(|_| GenerationNegativeError(()))?,
))
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct GenerationOverflowError(());
impl fmt::Display for GenerationOverflowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("generation number too large")
}
}
impl core::error::Error for GenerationOverflowError {}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct GenerationNegativeError(());
impl fmt::Display for GenerationNegativeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("negative generation number")
}
}
impl core::error::Error for GenerationNegativeError {}
#[repr(transparent)]
pub struct TypedGeneration<T: TypedGenerationKind> {
generation: Generation,
_phantom: PhantomData<T>,
}
impl<T: TypedGenerationKind> TypedGeneration<T> {
pub const ZERO: Self = Self {
generation: Generation::ZERO,
_phantom: PhantomData,
};
pub const MAX: Self = Self {
generation: Generation::MAX,
_phantom: PhantomData,
};
#[inline]
#[must_use]
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self {
generation: Generation::new(),
_phantom: PhantomData,
}
}
#[inline]
#[must_use]
pub const fn from_u32(value: u32) -> Self {
Self {
generation: Generation::from_u32(value),
_phantom: PhantomData,
}
}
#[inline]
#[must_use]
pub const fn next(&self) -> Self {
Self {
generation: self.generation.next(),
_phantom: PhantomData,
}
}
#[inline]
#[must_use]
pub const fn checked_next(&self) -> Option<Self> {
match self.generation.checked_next() {
Some(generation) => Some(Self {
generation,
_phantom: PhantomData,
}),
None => None,
}
}
#[inline]
#[must_use]
pub const fn prev(&self) -> Option<Self> {
match self.generation.prev() {
Some(generation) => Some(Self {
generation,
_phantom: PhantomData,
}),
None => None,
}
}
#[inline]
pub const fn as_u64(self) -> u64 {
self.generation.as_u64()
}
#[inline]
pub const fn as_i64(self) -> i64 {
self.generation.as_i64()
}
#[inline]
#[must_use]
pub const fn upcast<U: TypedGenerationKind>(self) -> TypedGeneration<U>
where
T: Into<U>,
{
TypedGeneration {
generation: self.generation,
_phantom: PhantomData,
}
}
}
impl<T: TypedGenerationKind> PartialEq for TypedGeneration<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.generation.eq(&other.generation)
}
}
impl<T: TypedGenerationKind> Eq for TypedGeneration<T> {}
impl<T: TypedGenerationKind> PartialOrd for TypedGeneration<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: TypedGenerationKind> Ord for TypedGeneration<T> {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.generation.cmp(&other.generation)
}
}
impl<T: TypedGenerationKind> Hash for TypedGeneration<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.generation.hash(state);
}
}
impl<T: TypedGenerationKind> fmt::Debug for TypedGeneration<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.generation, T::TAG)
}
}
impl<T: TypedGenerationKind> fmt::Display for TypedGeneration<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.generation.fmt(f)
}
}
impl<T: TypedGenerationKind> Clone for TypedGeneration<T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T: TypedGenerationKind> Copy for TypedGeneration<T> {}
impl<T: TypedGenerationKind> FromStr for TypedGeneration<T> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let generation =
Generation::from_str(s).map_err(|error| ParseError { error, tag: T::TAG })?;
Ok(Self::from_untyped_generation(generation))
}
}
impl<T: TypedGenerationKind> From<u32> for TypedGeneration<T> {
#[inline]
fn from(value: u32) -> Self {
Self::from_u32(value)
}
}
impl<T: TypedGenerationKind> From<TypedGeneration<T>> for u64 {
#[inline]
fn from(g: TypedGeneration<T>) -> Self {
g.as_u64()
}
}
impl<T: TypedGenerationKind> From<TypedGeneration<T>> for i64 {
#[inline]
fn from(g: TypedGeneration<T>) -> Self {
g.as_i64()
}
}
impl<T: TypedGenerationKind> From<&TypedGeneration<T>> for i64 {
#[inline]
fn from(g: &TypedGeneration<T>) -> Self {
g.as_i64()
}
}
impl<T: TypedGenerationKind> TryFrom<u64> for TypedGeneration<T> {
type Error = GenerationOverflowError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Ok(Self::from_untyped_generation(Generation::try_from(value)?))
}
}
impl<T: TypedGenerationKind> TryFrom<i64> for TypedGeneration<T> {
type Error = GenerationNegativeError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Ok(Self::from_untyped_generation(Generation::try_from(value)?))
}
}
#[cfg(feature = "serde")]
mod serde_imp {
use super::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
impl<'de> Deserialize<'de> for Generation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = u64::deserialize(deserializer)?;
Generation::try_from(value).map_err(|GenerationOverflowError(_)| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(value),
&"an integer between 0 and 9223372036854775807",
)
})
}
}
impl Serialize for Generation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de, T: TypedGenerationKind> Deserialize<'de> for TypedGeneration<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::from_untyped_generation(Generation::deserialize(
deserializer,
)?))
}
}
impl<T: TypedGenerationKind> Serialize for TypedGeneration<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.generation.serialize(serializer)
}
}
}
#[cfg(feature = "schemars08")]
mod schemars08_imp {
use super::*;
use schemars::{
JsonSchema, SchemaGenerator,
schema::{InstanceType, Metadata, NumberValidation, Schema, SchemaObject},
schema_for,
};
const GENERATION_DESCRIPTION: &str =
"Generation numbers stored in the database, used for optimistic concurrency control";
const CRATE_NAME: &str = "oxide-generation";
const CRATE_VERSION: &str = "0.1";
const CRATE_PATH: &str = "oxide_generation::TypedGeneration";
impl JsonSchema for Generation {
#[inline]
fn schema_name() -> String {
"Generation".to_owned()
}
#[inline]
fn schema_id() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("oxide_generation::Generation")
}
#[inline]
fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject {
metadata: Some(Box::new(Metadata {
description: Some(GENERATION_DESCRIPTION.to_owned()),
..Default::default()
})),
..generation_schema_object()
}
.into()
}
}
impl<T> JsonSchema for TypedGeneration<T>
where
T: TypedGenerationKind + JsonSchema,
{
#[inline]
fn schema_name() -> String {
if let Some(alias) = T::ALIAS {
alias.to_owned()
} else {
format!("TypedGenerationFor{}", T::schema_name())
}
}
#[inline]
fn schema_id() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Owned(format!(
"oxide_generation::TypedGeneration<{}>",
T::schema_id()
))
}
#[inline]
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
let t_schema = schema_for!(T);
if let Some(schema) = lift_json_schema(&t_schema.schema, T::ALIAS) {
return schema.into();
}
SchemaObject {
extensions: [(
"x-rust-type".to_string(),
serde_json::json!({
"crate": CRATE_NAME,
"version": CRATE_VERSION,
"path": CRATE_PATH,
"parameters": [generator.subschema_for::<T>()]
}),
)]
.into_iter()
.collect(),
..generation_schema_object()
}
.into()
}
}
fn generation_schema_object() -> SchemaObject {
SchemaObject {
instance_type: Some(InstanceType::Integer.into()),
format: Some("uint64".to_string()),
number: Some(Box::new(NumberValidation {
minimum: Some(0.0),
..Default::default()
})),
..Default::default()
}
}
#[allow(clippy::question_mark)]
fn lift_json_schema(schema: &SchemaObject, alias: Option<&str>) -> Option<SchemaObject> {
let Some(alias) = alias else {
return None;
};
let Some(v) = schema.extensions.get("x-rust-type") else {
return None;
};
let Some(crate_) = v.get("crate") else {
return None;
};
let Some(version) = v.get("version") else {
return None;
};
let Some(path) = v.get("path").and_then(|p| p.as_str()) else {
return None;
};
let Some((module_path, _)) = path.rsplit_once("::") else {
return None;
};
let alias_path = format!("{module_path}::{alias}");
Some(SchemaObject {
extensions: [(
"x-rust-type".to_string(),
serde_json::json!({
"crate": crate_,
"version": version,
"path": alias_path,
}),
)]
.into_iter()
.collect(),
..generation_schema_object()
})
}
}
#[cfg(feature = "proptest1")]
mod proptest1_imp {
use super::*;
use proptest::{
arbitrary::Arbitrary,
strategy::{BoxedStrategy, Strategy},
};
#[derive(Clone, Debug, Default)]
pub struct GenerationParams(());
impl Arbitrary for Generation {
type Parameters = GenerationParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(0..=Generation::MAX.as_u64()).prop_map(Generation).boxed()
}
}
#[derive(Clone, Debug, Default)]
pub struct TypedGenerationParams(());
impl<T> Arbitrary for TypedGeneration<T>
where
T: TypedGenerationKind,
{
type Parameters = TypedGenerationParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(0..=Generation::MAX.as_u64())
.prop_map(|value| TypedGeneration::<T>::from_untyped_generation(Generation(value)))
.boxed()
}
}
}
#[cfg(feature = "daft01")]
mod daft01_imp {
use super::*;
impl daft::Diffable for Generation {
type Diff<'daft> = daft::Leaf<&'daft Self>;
fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> {
daft::Leaf {
before: self,
after: other,
}
}
}
impl<T: TypedGenerationKind> daft::Diffable for TypedGeneration<T> {
type Diff<'daft> = daft::Leaf<&'daft Self>;
fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> {
daft::Leaf {
before: self,
after: other,
}
}
}
}
#[cfg(feature = "slog2")]
mod slog2_imp {
use super::*;
impl slog::Value for Generation {
fn serialize(
&self,
_rec: &slog::Record,
key: slog::Key,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
serializer.emit_u64(key, self.as_u64())
}
}
impl<T: TypedGenerationKind> slog::Value for TypedGeneration<T> {
fn serialize(
&self,
_rec: &slog::Record,
key: slog::Key,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
serializer.emit_u64(key, self.as_u64())
}
}
}
pub trait TypedGenerationKind: Send + Sync + 'static {
const TAG: TypedGenerationTag;
const ALIAS: Option<&'static str> = None;
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TypedGenerationTag(&'static str);
impl TypedGenerationTag {
#[must_use]
pub const fn new(tag: &'static str) -> Self {
match Self::try_new_impl(tag) {
Ok(tag) => tag,
Err(message) => panic!("{}", message),
}
}
pub const fn try_new(tag: &'static str) -> Result<Self, TagError> {
match Self::try_new_impl(tag) {
Ok(tag) => Ok(tag),
Err(message) => Err(TagError {
input: tag,
message,
}),
}
}
const fn try_new_impl(tag: &'static str) -> Result<Self, &'static str> {
if tag.is_empty() {
return Err("tag must not be empty");
}
let bytes = tag.as_bytes();
if !(bytes[0].is_ascii_alphabetic() || bytes[0] == b'_') {
return Err("first character of tag must be an ASCII letter or underscore");
}
let mut bytes = match bytes {
[_, rest @ ..] => rest,
[] => panic!("already checked that it's non-empty"),
};
while let [rest @ .., last] = &bytes {
if !(last.is_ascii_alphanumeric() || *last == b'_' || *last == b'-') {
break;
}
bytes = rest;
}
if !bytes.is_empty() {
return Err("tag must only contain ASCII letters, digits, underscores, or hyphens");
}
Ok(Self(tag))
}
pub const fn as_str(&self) -> &'static str {
self.0
}
}
impl fmt::Display for TypedGenerationTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
impl AsRef<str> for TypedGenerationTag {
fn as_ref(&self) -> &str {
self.0
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct TagError {
pub input: &'static str,
pub message: &'static str,
}
impl fmt::Display for TagError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"error creating tag from '{}': {}",
self.input, self.message
)
}
}
impl core::error::Error for TagError {}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ParseError {
pub error: ParseIntError,
pub tag: TypedGenerationTag,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "error parsing generation number ({})", self.tag)
}
}
impl core::error::Error for ParseError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.error)
}
}
pub trait GenericGeneration {
#[must_use]
fn from_untyped_generation(generation: Generation) -> Self
where
Self: Sized;
#[must_use]
fn into_untyped_generation(self) -> Generation
where
Self: Sized;
fn as_untyped_generation(&self) -> &Generation;
}
impl GenericGeneration for Generation {
#[inline]
fn from_untyped_generation(generation: Generation) -> Self {
generation
}
#[inline]
fn into_untyped_generation(self) -> Generation {
self
}
#[inline]
fn as_untyped_generation(&self) -> &Generation {
self
}
}
impl<T: TypedGenerationKind> GenericGeneration for TypedGeneration<T> {
#[inline]
fn from_untyped_generation(generation: Generation) -> Self {
Self {
generation,
_phantom: PhantomData,
}
}
#[inline]
fn into_untyped_generation(self) -> Generation {
self.generation
}
#[inline]
fn as_untyped_generation(&self) -> &Generation {
&self.generation
}
}
#[cfg(test)]
mod tests {
use super::*;
enum MyKind {}
impl TypedGenerationKind for MyKind {
const TAG: TypedGenerationTag = TypedGenerationTag::new("my_kind");
}
#[test]
fn test_validate_tags() {
for &valid_tag in &[
"a", "a-", "a_", "a-b", "a_b", "a1", "a1-", "a1_", "a1-b", "a1_b", "_a",
] {
TypedGenerationTag::try_new(valid_tag).expect("tag is valid");
_ = TypedGenerationTag::new(valid_tag);
}
for invalid_tag in &["", "1", "-", "a1b!", "a1-b!", "a1_b:", "\u{1f4a9}"] {
TypedGenerationTag::try_new(invalid_tag).unwrap_err();
}
}
#[test]
#[cfg(feature = "std")]
fn test_generic_generation_object_safe() {
let generation = Generation::new();
let box_generation = Box::new(generation) as Box<dyn GenericGeneration>;
assert_eq!(box_generation.as_untyped_generation(), &generation);
}
#[test]
fn test_next_and_prev() {
let first = Generation::new();
assert_eq!(first.as_u64(), 1);
assert_eq!(first.prev(), None);
assert_eq!(first.next(), Generation::from_u32(2));
assert_eq!(first.checked_next(), Some(Generation::from_u32(2)));
let zero = Generation::ZERO;
assert_eq!(zero.as_u64(), 0);
assert_eq!(zero.prev(), None);
assert_eq!(zero.next(), first);
let almost_max = Generation::try_from(Generation::MAX.as_u64() - 1).unwrap();
assert_eq!(almost_max.next(), Generation::MAX);
assert_eq!(almost_max.checked_next(), Some(Generation::MAX));
assert_eq!(almost_max.prev().unwrap().as_u64(), i64::MAX as u64 - 2);
assert_eq!(Generation::MAX.checked_next(), None);
assert_eq!(Generation::MAX.prev(), Some(almost_max));
let typed_max = TypedGeneration::<MyKind>::MAX;
assert_eq!(typed_max.as_u64(), Generation::MAX.as_u64());
assert_eq!(typed_max.checked_next(), None);
assert_eq!(
typed_max.prev().map(|g| g.as_u64()),
Some(Generation::MAX.as_u64() - 1)
);
assert_eq!(TypedGeneration::<MyKind>::new().as_u64(), 1);
assert_eq!(TypedGeneration::<MyKind>::ZERO.as_u64(), 0);
assert_eq!(TypedGeneration::<MyKind>::ZERO.next().as_u64(), 1);
}
#[test]
#[should_panic(expected = "attempt to overflow generation number")]
fn test_next_at_max_panics() {
_ = Generation::MAX.next();
}
#[test]
#[should_panic(expected = "attempt to overflow generation number")]
fn test_typed_next_at_max_panics() {
_ = TypedGeneration::<MyKind>::MAX.next();
}
#[test]
fn test_try_from() {
assert_eq!(Generation::try_from(0_u64).unwrap(), Generation::ZERO);
assert_eq!(
Generation::try_from(i64::MAX as u64).unwrap(),
Generation::MAX
);
Generation::try_from(i64::MAX as u64 + 1).unwrap_err();
Generation::try_from(u64::MAX).unwrap_err();
assert_eq!(Generation::try_from(0_i64).unwrap().as_u64(), 0);
assert_eq!(Generation::try_from(i64::MAX).unwrap(), Generation::MAX);
Generation::try_from(-1_i64).unwrap_err();
Generation::try_from(i64::MIN).unwrap_err();
assert_eq!(
TypedGeneration::<MyKind>::try_from(i64::MAX as u64).unwrap(),
TypedGeneration::<MyKind>::MAX
);
TypedGeneration::<MyKind>::try_from(i64::MAX as u64 + 1).unwrap_err();
TypedGeneration::<MyKind>::try_from(-1_i64).unwrap_err();
}
#[test]
fn test_conversions() {
let generation = Generation::from_u32(5);
assert_eq!(u64::from(generation), 5);
assert_eq!(i64::from(generation), 5);
assert_eq!(i64::from(&generation), 5);
assert_eq!(Generation::from(5_u32), generation);
let typed = TypedGeneration::<MyKind>::from_u32(5);
assert_eq!(u64::from(typed), 5);
assert_eq!(i64::from(typed), 5);
assert_eq!(i64::from(&typed), 5);
assert_eq!(TypedGeneration::<MyKind>::from(5_u32), typed);
assert_eq!(typed.into_untyped_generation(), generation);
assert_eq!(typed.as_untyped_generation(), &generation);
assert_eq!(typed.as_i64(), 5);
}
#[test]
fn test_from_str() {
assert_eq!(Generation::from_str("0").unwrap().as_u64(), 0);
assert_eq!(Generation::from_str("1").unwrap(), Generation::new());
assert_eq!(
Generation::from_str("9223372036854775807").unwrap(),
Generation::MAX
);
Generation::from_str("-1").unwrap_err();
Generation::from_str("9223372036854775808").unwrap_err();
assert_eq!(
"9223372036854775807"
.parse::<TypedGeneration<MyKind>>()
.unwrap(),
TypedGeneration::<MyKind>::MAX
);
"-1".parse::<TypedGeneration<MyKind>>().unwrap_err();
"9223372036854775808"
.parse::<TypedGeneration<MyKind>>()
.unwrap_err();
}
#[test]
#[cfg(feature = "std")]
fn test_display_and_debug() {
assert_eq!(Generation::new().to_string(), "1");
let generation = Generation::from_u32(5);
assert_eq!(generation.to_string(), "5");
let typed = TypedGeneration::<MyKind>::from_u32(5);
assert_eq!(typed.to_string(), "5");
assert_eq!(format!("{typed:?}"), "5 (my_kind)");
}
#[test]
#[cfg(feature = "std")]
fn test_error_displays() {
assert_eq!(
Generation::try_from(i64::MAX as u64 + 1)
.unwrap_err()
.to_string(),
"generation number too large"
);
assert_eq!(
Generation::try_from(-1_i64).unwrap_err().to_string(),
"negative generation number"
);
assert_eq!(
"-1".parse::<TypedGeneration<MyKind>>()
.unwrap_err()
.to_string(),
"error parsing generation number (my_kind)"
);
}
#[test]
#[cfg(all(feature = "serde", feature = "std"))]
fn test_serde() {
let generation = Generation::from_u32(5);
assert_eq!(serde_json::to_string(&generation).unwrap(), "5");
assert_eq!(serde_json::from_str::<Generation>("5").unwrap(), generation);
assert_eq!(serde_json::to_string(&Generation::new()).unwrap(), "1");
assert_eq!(
serde_json::from_str::<Generation>("1").unwrap(),
Generation::new()
);
assert_eq!(
serde_json::from_str::<Generation>("0").unwrap(),
Generation::ZERO
);
assert_eq!(
serde_json::from_str::<Generation>(&Generation::MAX.as_u64().to_string()).unwrap(),
Generation::MAX
);
for bad_value in [Generation::MAX.as_u64() + 1, u64::MAX] {
serde_json::from_str::<Generation>(&bad_value.to_string()).unwrap_err();
}
for bad_value in [-1_i64, i64::MIN] {
serde_json::from_str::<Generation>(&bad_value.to_string()).unwrap_err();
}
let typed = TypedGeneration::<MyKind>::from_u32(5);
assert_eq!(serde_json::to_string(&typed).unwrap(), "5");
assert_eq!(
serde_json::from_str::<TypedGeneration<MyKind>>("5").unwrap(),
typed
);
let error = serde_json::from_str::<Generation>("9223372036854775808").unwrap_err();
assert_eq!(
error.to_string(),
"invalid value: integer `9223372036854775808`, \
expected an integer between 0 and 9223372036854775807"
);
serde_json::from_str::<TypedGeneration<MyKind>>("9223372036854775808").unwrap_err();
}
#[test]
#[cfg(feature = "daft01")]
fn test_daft() {
use daft::Diffable;
let before = Generation::from_u32(5);
let after = Generation::from_u32(6);
let diff = before.diff(&after);
assert_eq!(diff.before, &before);
assert_eq!(diff.after, &after);
let typed_before = TypedGeneration::<MyKind>::from_u32(5);
let typed_after = TypedGeneration::<MyKind>::from_u32(6);
let typed_diff = typed_before.diff(&typed_after);
assert_eq!(typed_diff.before, &typed_before);
assert_eq!(typed_diff.after, &typed_after);
}
#[test]
#[cfg(feature = "slog2")]
fn test_slog() {
struct RecordingSerializer(Option<u64>);
impl slog::Serializer for RecordingSerializer {
fn emit_u64(&mut self, _key: slog::Key, value: u64) -> slog::Result {
self.0 = Some(value);
Ok(())
}
fn emit_arguments(&mut self, _key: slog::Key, _value: &fmt::Arguments) -> slog::Result {
panic!("generation numbers are emitted via emit_u64")
}
}
#[allow(clippy::useless_conversion)]
fn emitted_u64(value: &dyn slog::Value) -> Option<u64> {
let mut serializer = RecordingSerializer(None);
value
.serialize(
&slog::record!(
slog::Level::Info,
"",
&format_args!(""),
slog::BorrowedKV(&())
),
"generation".into(),
&mut serializer,
)
.expect("generation number was serialized");
serializer.0
}
assert_eq!(emitted_u64(&Generation::from_u32(5)), Some(5));
assert_eq!(
emitted_u64(&TypedGeneration::<MyKind>::from_u32(6)),
Some(6)
);
}
#[test]
#[cfg(feature = "schemars08")]
fn test_generation_schema() {
let schema = <Generation as schemars::JsonSchema>::json_schema(
&mut schemars::SchemaGenerator::default(),
);
assert_eq!(
serde_json::to_value(&schema).unwrap(),
serde_json::json!({
"description": "Generation numbers stored in the database, \
used for optimistic concurrency control",
"type": "integer",
"format": "uint64",
"minimum": 0.0,
})
);
}
}