Arnavion / k8s-openapi

Rust definitions of the resource types in the Kubernetes client API
Apache License 2.0
373 stars 42 forks source link

`apimachinery::pkg::api::resource::Quantity` compatibility with Go #135

Open imp opened 1 year ago

imp commented 1 year ago

The type in question in Go is significantly more complex than just string. It might be beneficial to mimic its Go behavior in this library as well. Perhaps adding various methods similar to its Go counterpart. That will be backwards compatible I think. Or perhaps even replacing the type itself to imitate Go more closely, however that will definitely be incompatible change.

Arnavion commented 1 year ago

Ref:


Perhaps adding various methods similar to its Go counterpart.

Methods are not the hard part. The serialization and deserialization is, because it has to enforce all the rules that the type has. The deserializer has to replicate the parsing of the suffix, and the serializer has to perform canonicalization.

Eg, let's say we represent it as:

pub struct Quantity {
    pub value: i64, // Incorporates the exponent
    pub suffix: Suffix,
}

pub enum Suffix {
    None,
    Kibi,
    Mibi,
    Kilo,
    Mega,
    ...
}

This cannot be naively serialized by just serializing the value and the suffix one after the other, because Quantity { value: 1000, suffix: Suffix::None } needs to be serialized as "1e3" and not "1000", Quantity { value: 1000, suffix: Suffix::Kilo } needs to be serialized as "1M" and not "1000k", etc.

All that logic is in the files I linked above plus the go-inf library, so all of that would need to be translated to Rust and would need to be kept in sync with changes.


That will be backwards compatible I think. Or perhaps even replacing the type itself to imitate Go more closely, however that will definitely be incompatible change.

Compatibility is not a concern. Almost every release of this crate is already semver-major.

Arnavion commented 5 months ago

$dayjob needed code for the specific purpose of deserializing a Quantity into a u64 in a lossy, saturating way, so I ended up writing two versions related to that. Neither is complete enough for me to publish to crates.io, but I'm putting them here so that I don't lose them.

Version 1: Deserializes into a lossless typed representation. Conversion to `u64` doesn't support binary suffixes. ```rust use std::{collections::VecDeque, str::FromStr}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Quantity { pub sign: Sign, pub integral: Vec, pub fractional: Vec, pub suffix: Suffix, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Sign { Positive, Negative, } #[derive(Clone, Debug, Eq, PartialEq)] pub enum Suffix { BinarySI(BinarySISuffix), DecimalExponent(DecimalExponentSuffix), DecimalSI(DecimalSISuffix), } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum BinarySISuffix { Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct DecimalExponentSuffix { pub sign: Sign, pub digits: Vec, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DecimalSISuffix { Nano, Micro, Milli, None, Kilo, Mega, Giga, Tera, Peta, Exa, } fn split_first(s: &[u8]) -> Option<(u8, &[u8])> { s.split_first().map(|(first, rest)| (*first, rest)) } impl FromStr for Quantity { type Err = String; fn from_str(s: &str) -> Result { let rest = &mut s.as_bytes(); let sign = parse_sign(rest); let integral = parse_digits(rest).unwrap_or_default(); let decimal_point; (decimal_point, *rest) = match split_first(*rest) { Some((b'.', rest)) => (true, rest), _ => (false, *rest), }; let fractional = if decimal_point { parse_digits(rest).unwrap_or_default() } else if integral.is_empty() { return Err(format!( "expected signed number, found {:?}", rest.escape_ascii().to_string(), )); } else { vec![] }; let suffix = parse_suffix(rest)?; Ok(Self { sign, integral, fractional, suffix, }) } } fn parse_sign(rest: &mut &[u8]) -> Sign { let sign; (sign, *rest) = match split_first(*rest) { Some((b'+', rest)) => (Sign::Positive, rest), Some((b'-', rest)) => (Sign::Negative, rest), _ => (Sign::Positive, *rest), }; sign } fn parse_suffix(rest: &mut &[u8]) -> Result { #[allow(clippy::match_same_arms)] { *rest = match split_first(rest) { Some((b'K', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Kibi)), Some((b'M', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Mebi)), Some((b'G', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Gibi)), Some((b'T', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Tebi)), Some((b'P', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Pebi)), Some((b'E', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Exbi)), Some((b'n', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Nano)), Some((b'u', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Micro)), Some((b'm', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Milli)), None => return Ok(Suffix::DecimalSI(DecimalSISuffix::None)), Some((b'k', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Kilo)), Some((b'M', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Mega)), Some((b'G', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Giga)), Some((b'T', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Tera)), Some((b'P', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Peta)), Some((b'E', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Exa)), Some((b'e' | b'E', rest)) => rest, Some(_) => { return Err(format!( "expected suffix, found {:?}", rest.escape_ascii().to_string(), )) } }; } let sign = parse_sign(rest); let digits = parse_digits(rest)?; if !rest.is_empty() { return Err(format!( "trailing garbage: {:?}", rest.escape_ascii().to_string() )); } Ok(Suffix::DecimalExponent(DecimalExponentSuffix { sign, digits, })) } fn parse_digits(rest: &mut &[u8]) -> Result, String> { let mut result = vec![]; loop { let digit; (digit, *rest) = match split_first(*rest) { Some((digit @ b'0'..=b'9', rest)) => (Some(digit), rest), _ if !result.is_empty() => (None, *rest), _ => return Err("digits is empty".to_owned()), }; if let Some(digit) = digit { result.push(digit - b'0'); } else { break; } } Ok(result) } impl From for u64 { fn from(quantity: Quantity) -> Self { let Quantity { sign, mut integral, fractional, mut suffix, } = quantity; let mut fractional: VecDeque<_> = fractional.into(); if integral.iter().all(|&digit| digit == 0) && fractional.iter().all(|&digit| digit == 0) { return 0; } if matches!(sign, Sign::Negative) { return 0; } loop { match suffix { Suffix::BinarySI(_) => unimplemented!(), Suffix::DecimalExponent(DecimalExponentSuffix { sign, digits }) => { let value = digits.into_iter().fold(0_u8, |value, digit| { value.saturating_mul(10).saturating_add(digit) }); for _ in 0..value { match sign { Sign::Positive => integral.push(fractional.pop_front().unwrap_or(0)), Sign::Negative => fractional.push_front(integral.pop().unwrap_or(0)), } } break; } Suffix::DecimalSI(DecimalSISuffix::Nano) => { fractional.push_front(integral.pop().unwrap_or(0)); fractional.push_front(integral.pop().unwrap_or(0)); fractional.push_front(integral.pop().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::Micro); } Suffix::DecimalSI(DecimalSISuffix::Micro) => { fractional.push_front(integral.pop().unwrap_or(0)); fractional.push_front(integral.pop().unwrap_or(0)); fractional.push_front(integral.pop().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::Milli); } Suffix::DecimalSI(DecimalSISuffix::Milli) => { fractional.push_front(integral.pop().unwrap_or(0)); fractional.push_front(integral.pop().unwrap_or(0)); fractional.push_front(integral.pop().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::None); } Suffix::DecimalSI(DecimalSISuffix::None) => break, Suffix::DecimalSI(DecimalSISuffix::Kilo) => { integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::None); } Suffix::DecimalSI(DecimalSISuffix::Mega) => { integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::Kilo); } Suffix::DecimalSI(DecimalSISuffix::Giga) => { integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::Mega); } Suffix::DecimalSI(DecimalSISuffix::Tera) => { integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::Giga); } Suffix::DecimalSI(DecimalSISuffix::Peta) => { integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::Tera); } Suffix::DecimalSI(DecimalSISuffix::Exa) => { integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); integral.push(fractional.pop_front().unwrap_or(0)); suffix = Suffix::DecimalSI(DecimalSISuffix::Peta); } } } let mut integral = integral.into_iter().fold(0_u64, |value, digit| { value.saturating_mul(10).saturating_add(digit.into()) }); if fractional.iter().any(|&digit| digit != 0) { integral = integral.saturating_add(1); } integral } } #[cfg(test)] mod tests { use super::*; // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go #[test] fn ok() { #[rustfmt::skip] let test_cases = [ ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("10", 10, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("500", 500, Quantity { sign: Sign::Positive, integral: vec![5, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("8000", 8000, Quantity { sign: Sign::Positive, integral: vec![8, 0, 0 ,0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("2", 2, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.03", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 3], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.004", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 4], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("9223372036854775808", 9223372036854775808, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("92233720368547758080", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("922337203685477580800", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("922337203685477580.8", 922337203685477581, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![8], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("92233720368547758.08", 92233720368547759, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 8], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("-9223372036854775809", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("-92233720368547758090", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("-922337203685477580900", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("-922337203685477580.9", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![9], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("-92233720368547758.09", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 9], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.025Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), ("1.025Ti", 0, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), ("-1.025Ti", 0, Quantity { sign: Sign::Negative, integral: vec![1], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), (".", 0, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("-.", 0, Quantity { sign: Sign::Negative, integral: vec![], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![3] }) }), ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0n", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }), ("0u", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Micro) }), ("0m", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Milli) }), ("0Ki", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }), ("0k", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("0Mi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Mebi) }), ("0M", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }), ("0Gi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Gibi) }), ("0G", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }), ("0Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), ("0T", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }), ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("1Ki", 0, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }), ("8Ki", 0, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }), ("7Mi", 0, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Mebi) }), ("6Gi", 0, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Gibi) }), ("5Ti", 0, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), ("4Pi", 0, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Pebi) }), ("3Ei", 0, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Exbi) }), ("10Ti", 0, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), ("100Ti", 0, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), ("5n", 1, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }), ("4u", 1, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Micro) }), ("3m", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Milli) }), ("9", 9, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("8k", 8000, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("50k", 50000, Quantity { sign: Sign::Positive, integral: vec![5, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("7M", 7000000, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }), ("6G", 6000000000, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }), ("5T", 5000000000000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }), ("40T", 40000000000000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }), ("300T", 300000000000000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }), ("2P", 2000000000000000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Peta) }), ("1E", 1000000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Exa) }), ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![3] }) }), ("1e3", 1000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![3] }) }), ("1E6", 1000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![6] }) }), ("1e9", 1000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![9] }) }), ("1E12", 1000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 2] }) }), ("1e15", 1000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 5] }) }), ("1E18", 1000000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 8] }) }), ("1e14", 100000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 4] }) }), ("1e13", 10000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 3] }) }), ("1e15", 1000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 5] }) }), ("1e3", 1000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![3] }) }), ("100.035k", 100035, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("0.001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.0005k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("0.005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.05", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.5", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.00050k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("0.00500", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5, 0, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.05000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5, 0, 0, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.50000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5, 0, 0, 0, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![0] }) }), ("0.5e-1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![1] }) }), ("0.5e-2", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![2] }) }), ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![0] }) }), ("10.035M", 10035000, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![0, 3, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }), ("1.2e3", 1200, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![2], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![3] }) }), ("1.3E+6", 1300000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![3], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![6] }) }), ("1.40e9", 1400000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![4, 0], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![9] }) }), ("1.53E12", 1530000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![5, 3], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 2] }) }), ("1.6e15", 1600000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![6], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 5] }) }), ("1.7E18", 1700000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![7], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 8] }) }), ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("8.1k", 8100, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![1], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("7.123456M", 7123456, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![1, 2, 3, 4, 5, 6], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }), ("6.987654321G", 6987654321, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![9, 8, 7, 6, 5, 4, 3, 2, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }), ("5.444T", 5444000000000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![4, 4, 4], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }), ("40.1T", 40100000000000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![1], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }), ("300.2T", 300200000000000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![2], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }), ("2.5P", 2500000000000000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![5], suffix: Suffix::DecimalSI(DecimalSISuffix::Peta) }), ("1.01E", 1010000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Exa) }), ("3.001n", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }), ("1.1E-9", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![1], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![9] }) }), ("0.0000000001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.0000000005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.00000000050", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.5e-9", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![9] }) }), ("0.9n", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![9], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }), ("0.00000012345", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("0.00000012354", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 5, 4], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("9Ei", 0, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Exbi) }), ("9223372036854775807Ki", 0, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }), ("12E", 12000000000000000000, Quantity { sign: Sign::Positive, integral: vec![1, 2], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Exa) }), ("100.035Ki", 0, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }), ("0.5Mi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::BinarySI(BinarySISuffix::Mebi) }), ("0.05Gi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], suffix: Suffix::BinarySI(BinarySISuffix::Gibi) }), ("0.025Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }), ("0.000000000001Ki", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }), (".001", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), (".0001k", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }), ("1.", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("1.G", 1000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }), ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ("-9.01", 0, Quantity { sign: Sign::Negative, integral: vec![9], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }), ]; for (input, expected_u64, expected_quantity) in test_cases { eprintln!("input: {input}"); let actual_quantity: Quantity = input.parse().unwrap(); assert_eq!(expected_quantity, actual_quantity); if !matches!(actual_quantity.suffix, Suffix::BinarySI(_)) { let actual_u64: u64 = actual_quantity.into(); assert_eq!(expected_u64, actual_u64); } } } // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go #[test] fn err() { for (input, expected) in [ ("1.1.M", r#"expected suffix, found ".M""#), ("1+1.0M", r#"expected suffix, found "+1.0M""#), ("0.1mi", r#"expected suffix, found "mi""#), ("0.1am", r#"expected suffix, found "am""#), ("aoeu", r#"expected signed number, found "aoeu""#), (".5i", r#"expected suffix, found "i""#), ("1i", r#"expected suffix, found "i""#), ("-3.01i", r#"expected suffix, found "i""#), ("-3.01e-", r#"digits is empty"#), (" 1", r#"expected signed number, found " 1""#), ("1 ", r#"expected suffix, found " ""#), ] { eprintln!("input: {input}"); let actual = input.parse::().unwrap_err(); assert_eq!(expected, actual); } } } ```
Version 2: Deserializes into a lossy typed representation that treats binary suffixes as the closest decimal prefix. ```rust use std::{cmp::Ordering, collections::VecDeque, str::FromStr}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Quantity { pub sign: Sign, pub integral: Vec, pub fractional: Vec, pub exponent: i8, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Sign { Positive, Negative, } fn split_first(s: &[u8]) -> Option<(u8, &[u8])> { s.split_first().map(|(first, rest)| (*first, rest)) } impl FromStr for Quantity { type Err = String; fn from_str(s: &str) -> Result { let rest = &mut s.as_bytes(); let sign = parse_sign(rest); let integral = parse_digits(rest).unwrap_or_default(); let decimal_point; (decimal_point, *rest) = match split_first(rest) { Some((b'.', rest)) => (true, rest), _ => (false, *rest), }; let fractional = if decimal_point { parse_digits(rest).unwrap_or_default() } else if integral.is_empty() { return Err(format!( "expected signed number, found {:?}", rest.escape_ascii().to_string(), )); } else { vec![] }; let exponent = parse_exponent(rest)?; Ok(Self { sign, integral, fractional, exponent, }) } } fn parse_sign(rest: &mut &[u8]) -> Sign { let sign; (sign, *rest) = match split_first(rest) { Some((b'+', rest)) => (Sign::Positive, rest), Some((b'-', rest)) => (Sign::Negative, rest), _ => (Sign::Positive, *rest), }; sign } fn parse_exponent(rest: &mut &[u8]) -> Result { #[allow(clippy::match_same_arms)] { *rest = match split_first(rest) { // Binary suffixes are treated like decimal suffixes. // This means the computed value will be a little smaller than the intended value, // but the difference is small enough that it won't matter. Some((b'K', b"i")) => return Ok(3), Some((b'M', b"i")) => return Ok(6), Some((b'G', b"i")) => return Ok(9), Some((b'T', b"i")) => return Ok(12), Some((b'P', b"i")) => return Ok(15), Some((b'E', b"i")) => return Ok(18), Some((b'n', b"")) => return Ok(-9), Some((b'u', b"")) => return Ok(-6), Some((b'm', b"")) => return Ok(-3), None => return Ok(0), Some((b'k', b"")) => return Ok(3), Some((b'M', b"")) => return Ok(6), Some((b'G', b"")) => return Ok(9), Some((b'T', b"")) => return Ok(12), Some((b'P', b"")) => return Ok(15), Some((b'E', b"")) => return Ok(18), Some((b'e' | b'E', rest)) => rest, Some(_) => { return Err(format!( "expected suffix, found {:?}", rest.escape_ascii().to_string(), )) } }; } let sign = parse_sign(rest); let digits = parse_digits(rest)?; let value = digits.into_iter().fold(0_i8, |value, digit| { let value = value.saturating_mul(10); match sign { Sign::Positive => value.saturating_add(digit.try_into().expect("digit is 0..=9")), Sign::Negative => value.saturating_sub(digit.try_into().expect("digit is 0..=9")), } }); if !rest.is_empty() { return Err(format!( "trailing garbage: {:?}", rest.escape_ascii().to_string(), )); } Ok(value) } fn parse_digits(rest: &mut &[u8]) -> Result, String> { let mut result = vec![]; loop { let digit; (digit, *rest) = match split_first(rest) { Some((digit @ b'0'..=b'9', rest)) => (Some(digit), rest), _ if !result.is_empty() => (None, *rest), _ => return Err("digits is empty".to_owned()), }; if let Some(digit) = digit { result.push(digit - b'0'); } else { break; } } Ok(result) } impl From for u64 { fn from(quantity: Quantity) -> Self { let Quantity { sign, integral, fractional, exponent, } = quantity; let mut fractional: VecDeque<_> = fractional.into(); if integral.iter().all(|&digit| digit == 0) && fractional.iter().all(|&digit| digit == 0) { return 0; } if matches!(sign, Sign::Negative) { return 0; } let mut result = integral.into_iter().fold(0_u64, |value, digit| { value.saturating_mul(10).saturating_add(digit.into()) }); let mut has_fractional_digits = fractional.iter().any(|&digit| digit != 0); match exponent.cmp(&0) { Ordering::Less => { for _ in exponent..0 { let last_integer_digit = result % 10; result /= 10; has_fractional_digits |= last_integer_digit != 0; } } Ordering::Equal => (), Ordering::Greater => { for _ in 0..exponent { result = result .saturating_mul(10) .saturating_add(fractional.pop_front().unwrap_or(0).into()); } has_fractional_digits = fractional.iter().any(|&digit| digit != 0); } } if has_fractional_digits { result = result.saturating_add(1); } result } } #[cfg(test)] mod tests { use super::*; // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go #[test] fn ok() { #[rustfmt::skip] let test_cases = [ ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 0 }), ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 0 }), ("10", 10, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], exponent: 0 }), ("500", 500, Quantity { sign: Sign::Positive, integral: vec![5, 0, 0], fractional: vec![], exponent: 0 }), ("8000", 8_000, Quantity { sign: Sign::Positive, integral: vec![8, 0, 0 ,0], fractional: vec![], exponent: 0 }), ("2", 2, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], exponent: 0 }), ("0.1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![1], exponent: 0 }), ("0.03", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 3], exponent: 0 }), ("0.004", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 4], exponent: 0 }), ("9223372036854775808", 9_223_372_036_854_775_808, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8], fractional: vec![], exponent: 0 }), ("92233720368547758080", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0], fractional: vec![], exponent: 0 }), ("922337203685477580800", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0, 0], fractional: vec![], exponent: 0 }), ("922337203685477580.8", 922_337_203_685_477_581, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![8], exponent: 0 }), ("92233720368547758.08", 92_233_720_368_547_759, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 8], exponent: 0 }), ("-9223372036854775809", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9], fractional: vec![], exponent: 0 }), ("-92233720368547758090", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0], fractional: vec![], exponent: 0 }), ("-922337203685477580900", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0, 0], fractional: vec![], exponent: 0 }), ("-922337203685477580.9", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![9], exponent: 0 }), ("-92233720368547758.09", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 9], exponent: 0 }), ("0.025Ti", 25_000_000_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], exponent: 12 }), ("1.025Ti", 1_025_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 2, 5], exponent: 12 }), ("-1.025Ti", 0, Quantity { sign: Sign::Negative, integral: vec![1], fractional: vec![0, 2, 5], exponent: 12 }), (".", 0, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![], exponent: 0 }), ("-.", 0, Quantity { sign: Sign::Negative, integral: vec![], fractional: vec![], exponent: 0 }), ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: -3 }), ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 0 }), ("0n", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: -9 }), ("0u", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: -6 }), ("0m", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: -3 }), ("0Ki", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 3 }), ("0k", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 3 }), ("0Mi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 6 }), ("0M", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 6 }), ("0Gi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 9 }), ("0G", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 9 }), ("0Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 12 }), ("0T", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 12 }), ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 0 }), ("1Ki", 1_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 3 }), ("8Ki", 8_000, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], exponent: 3 }), ("7Mi", 7_000_000, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], exponent: 6 }), ("6Gi", 6_000_000_000, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], exponent: 9 }), ("5Ti", 5_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], exponent: 12 }), ("4Pi", 4_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], exponent: 15 }), ("3Ei", 3_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], exponent: 18 }), ("10Ti", 10_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], exponent: 12 }), ("100Ti", 100_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![], exponent: 12 }), ("5n", 1, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], exponent: -9 }), ("4u", 1, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], exponent: -6 }), ("3m", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], exponent: -3 }), ("9", 9, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], exponent: 0 }), ("8k", 8_000, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], exponent: 3 }), ("50k", 50_000, Quantity { sign: Sign::Positive, integral: vec![5, 0], fractional: vec![], exponent: 3 }), ("7M", 7_000_000, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], exponent: 6 }), ("6G", 6_000_000_000, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], exponent: 9 }), ("5T", 5_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], exponent: 12 }), ("40T", 40_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![], exponent: 12 }), ("300T", 300_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![], exponent: 12 }), ("2P", 2_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], exponent: 15 }), ("1E", 1_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 18 }), ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: -3 }), ("1e3", 1_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 3 }), ("1E6", 1_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 6 }), ("1e9", 1_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 9 }), ("1E12", 1_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 12 }), ("1e15", 1_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 15 }), ("1E18", 1_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 18 }), ("1e14", 100_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 14 }), ("1e13", 10_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 13 }), ("1e15", 1_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 15 }), ("1e3", 1_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 3 }), ("100.035k", 100_035, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], exponent: 3 }), ("0.001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 1], exponent: 0 }), ("0.0005k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5], exponent: 3 }), ("0.005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5], exponent: 0 }), ("0.05", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], exponent: 0 }), ("0.5", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 0 }), ("0.00050k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5, 0], exponent: 3 }), ("0.00500", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5, 0, 0], exponent: 0 }), ("0.05000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5, 0, 0, 0], exponent: 0 }), ("0.50000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5, 0, 0, 0, 0], exponent: 0 }), ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 0 }), ("0.5e-1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: -1 }), ("0.5e-2", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: -2 }), ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 0 }), ("10.035M", 10_035_000, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![0, 3, 5], exponent: 6 }), ("1.2e3", 1_200, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![2], exponent: 3 }), ("1.3E+6", 1_300_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![3], exponent: 6 }), ("1.40e9", 1_400_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![4, 0], exponent: 9 }), ("1.53E12", 1_530_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![5, 3], exponent: 12 }), ("1.6e15", 1_600_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![6], exponent: 15 }), ("1.7E18", 1_700_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![7], exponent: 18 }), ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], exponent: 0 }), ("8.1k", 8_100, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![1], exponent: 3 }), ("7.123456M", 7_123_456, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![1, 2, 3, 4, 5, 6], exponent: 6 }), ("6.987654321G", 6_987_654_321, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![9, 8, 7, 6, 5, 4, 3, 2, 1], exponent: 9 }), ("5.444T", 5_444_000_000_000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![4, 4, 4], exponent: 12 }), ("40.1T", 40_100_000_000_000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![1], exponent: 12 }), ("300.2T", 300_200_000_000_000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![2], exponent: 12 }), ("2.5P", 2_500_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![5], exponent: 15 }), ("1.01E", 1_010_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 1], exponent: 18 }), ("3.001n", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![0, 0, 1], exponent: -9 }), ("1.1E-9", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![1], exponent: -9 }), ("0.0000000001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 1], exponent: 0 }), ("0.0000000005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5], exponent: 0 }), ("0.00000000050", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0], exponent: 0 }), ("0.5e-9", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: -9 }), ("0.9n", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![9], exponent: -9 }), ("0.00000012345", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5], exponent: 0 }), ("0.00000012354", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 5, 4], exponent: 0 }), ("9Ei", 9_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], exponent: 18 }), ("9223372036854775807Ki", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7], fractional: vec![], exponent: 3 }), ("12E", 12_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1, 2], fractional: vec![], exponent: 18 }), ("100.035Ki", 100_035, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], exponent: 3 }), ("0.5Mi", 500_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 6 }), ("0.05Gi", 50_000_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], exponent: 9 }), ("0.025Ti", 25_000_000_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], exponent: 12 }), ("0.000000000001Ki", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], exponent: 3 }), (".001", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 1], exponent: 0 }), (".0001k", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 0, 1], exponent: 3 }), ("1.", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 0 }), ("1.G", 1_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 9 }), ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], exponent: 0 }), ("-9.01", 0, Quantity { sign: Sign::Negative, integral: vec![9], fractional: vec![0, 1], exponent: 0 }), ]; for (input, expected_u64, expected_quantity) in test_cases { eprintln!("input: {input}"); let actual_quantity: Quantity = input.parse().unwrap(); assert_eq!(expected_quantity, actual_quantity); let actual_u64: u64 = actual_quantity.into(); assert_eq!(expected_u64, actual_u64); } } // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go #[test] fn err() { for (input, expected) in [ ("1.1.M", r#"expected suffix, found ".M""#), ("1+1.0M", r#"expected suffix, found "+1.0M""#), ("0.1mi", r#"expected suffix, found "mi""#), ("0.1am", r#"expected suffix, found "am""#), ("aoeu", r#"expected signed number, found "aoeu""#), (".5i", r#"expected suffix, found "i""#), ("1i", r#"expected suffix, found "i""#), ("-3.01i", r#"expected suffix, found "i""#), ("-3.01e-", "digits is empty"), (" 1", r#"expected signed number, found " 1""#), ("1 ", r#"expected suffix, found " ""#), ] { eprintln!("input: {input}"); let actual = input.parse::().unwrap_err(); assert_eq!(expected, actual); } } } ```