@@ -20,17 +20,34 @@ pub use memory::*;
2020pub use suffix:: * ;
2121
2222#[ derive( Debug , PartialEq , Snafu ) ]
23- pub enum ParseQuantityError {
23+ pub enum ParseQuantityError < E >
24+ where
25+ E : std:: error:: Error + ' static ,
26+ {
2427 #[ snafu( display( "input is either empty or contains non-ascii characters" ) ) ]
2528 InvalidFormat ,
2629
2730 #[ snafu( display( "failed to parse floating point number" ) ) ]
2831 InvalidFloat { source : ParseFloatError } ,
2932
3033 #[ snafu( display( "failed to parse suffix" ) ) ]
31- InvalidSuffix { source : ParseSuffixError } ,
34+ InvalidSuffix { source : E } ,
3235}
3336
37+ // pub struct CpuQuant(Quantity1<DecimalMultiple>);
38+
39+ // pub struct Quantity1<T>
40+ // where
41+ // T: SuffixTrait,
42+ // {
43+ // value: f64,
44+ // suffix: T,
45+ // }
46+
47+ // pub trait SuffixTrait: FromStr + Default {
48+ // fn factor(&self) -> f64;
49+ // }
50+
3451/// Quantity is a representation of a number with a suffix / format.
3552///
3653/// This type makes it possible to parse Kubernetes quantity strings like '12Ki', '2M, '1.5e2', or
@@ -95,7 +112,10 @@ pub enum ParseQuantityError {
95112/// [quantity-format]: https://github.com/kubernetes/apimachinery/blob/3e8e52d6a1259ada73f63c1c7d1fad39d4ba9fb4/pkg/api/resource/quantity.go#L39-L59
96113/// [sci-notation]: https://en.wikipedia.org/wiki/Scientific_notation#E_notation
97114#[ derive( Clone , Copy , Debug , PartialEq , PartialOrd ) ]
98- pub struct Quantity {
115+ pub struct Quantity < S >
116+ where
117+ S : Suffix ,
118+ {
99119 // FIXME (@Techassi): Support arbitrary-precision numbers
100120 /// The numeric value of the quantity.
101121 ///
@@ -107,18 +127,21 @@ pub struct Quantity {
107127 /// The suffix of the quantity.
108128 ///
109129 /// This field holds data parsed from `<suffix>` according to the spec.
110- suffix : Suffix ,
130+ suffix : S ,
111131}
112132
113- impl FromStr for Quantity {
114- type Err = ParseQuantityError ;
133+ impl < S > FromStr for Quantity < S >
134+ where
135+ S : Suffix ,
136+ {
137+ type Err = ParseQuantityError < S :: Err > ;
115138
116139 fn from_str ( input : & str ) -> Result < Self , Self :: Err > {
117140 ensure ! ( !input. is_empty( ) && input. is_ascii( ) , InvalidFormatSnafu ) ;
118141
119142 if input == "0" {
120143 return Ok ( Self {
121- suffix : Suffix :: DecimalMultiple ( DecimalMultiple :: Empty ) ,
144+ suffix : S :: default ( ) ,
122145 value : 0.0 ,
123146 } ) ;
124147 }
@@ -127,23 +150,26 @@ impl FromStr for Quantity {
127150 Some ( suffix_index) => {
128151 let parts = input. split_at ( suffix_index) ;
129152 let value = f64:: from_str ( parts. 0 ) . context ( InvalidFloatSnafu ) ?;
130- let suffix = Suffix :: from_str ( parts. 1 ) . context ( InvalidSuffixSnafu ) ?;
153+ let suffix = S :: from_str ( parts. 1 ) . context ( InvalidSuffixSnafu ) ?;
131154
132155 Ok ( Self { suffix, value } )
133156 }
134157 None => {
135158 let value = f64:: from_str ( input) . context ( InvalidFloatSnafu ) ?;
136159
137160 Ok ( Self {
138- suffix : Suffix :: DecimalMultiple ( DecimalMultiple :: Empty ) ,
161+ suffix : S :: default ( ) ,
139162 value,
140163 } )
141164 }
142165 }
143166 }
144167}
145168
146- impl Display for Quantity {
169+ impl < S > Display for Quantity < S >
170+ where
171+ S : Suffix ,
172+ {
147173 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
148174 if self . value == 0.0 {
149175 return f. write_char ( '0' ) ;
@@ -158,42 +184,57 @@ impl Display for Quantity {
158184 }
159185}
160186
161- impl From < Quantity > for K8sQuantity {
162- fn from ( value : Quantity ) -> Self {
187+ impl < S > From < Quantity < S > > for K8sQuantity
188+ where
189+ S : Suffix ,
190+ {
191+ fn from ( value : Quantity < S > ) -> Self {
163192 K8sQuantity ( value. to_string ( ) )
164193 }
165194}
166195
167- impl From < & Quantity > for K8sQuantity {
168- fn from ( value : & Quantity ) -> Self {
196+ impl < S > From < & Quantity < S > > for K8sQuantity
197+ where
198+ S : Suffix ,
199+ {
200+ fn from ( value : & Quantity < S > ) -> Self {
169201 K8sQuantity ( value. to_string ( ) )
170202 }
171203}
172204
173- impl TryFrom < K8sQuantity > for Quantity {
174- type Error = ParseQuantityError ;
205+ impl < S > TryFrom < K8sQuantity > for Quantity < S >
206+ where
207+ S : Suffix ,
208+ {
209+ type Error = ParseQuantityError < S :: Err > ;
175210
176211 fn try_from ( value : K8sQuantity ) -> Result < Self , Self :: Error > {
177212 Quantity :: from_str ( & value. 0 )
178213 }
179214}
180215
181- impl TryFrom < & K8sQuantity > for Quantity {
182- type Error = ParseQuantityError ;
216+ impl < S > TryFrom < & K8sQuantity > for Quantity < S >
217+ where
218+ S : Suffix ,
219+ {
220+ type Error = ParseQuantityError < S :: Err > ;
183221
184222 fn try_from ( value : & K8sQuantity ) -> Result < Self , Self :: Error > {
185223 Quantity :: from_str ( & value. 0 )
186224 }
187225}
188226
189- impl Quantity {
227+ impl < S > Quantity < S >
228+ where
229+ S : Suffix ,
230+ {
190231 /// Optionally scales up or down to the provided `suffix`.
191232 ///
192233 /// No scaling is performed in the following cases:
193234 ///
194235 /// - the suffixes already match
195236 /// - the value is 0
196- pub fn scale_to ( self , suffix : Suffix ) -> Self {
237+ pub fn scale_to ( self , suffix : S ) -> Self {
197238 match ( self . value , & self . suffix ) {
198239 ( 0.0 , _) => self ,
199240 ( _, s) if * s == suffix => self ,
@@ -208,6 +249,28 @@ impl Quantity {
208249 }
209250 }
210251
252+ // pub fn scale_to_non_zero(self) -> Self {
253+ // if !self.value.between(-1.0, 1.0) {
254+ // return self;
255+ // }
256+
257+ // let mut this = self;
258+
259+ // while let Some(suffix) = this.suffix.scale_down() {
260+ // this = self.scale_to(suffix);
261+ // if this.value.between(-1.0, 1.0) {
262+ // continue;
263+ // } else {
264+ // return this;
265+ // }
266+ // }
267+
268+ // Self {
269+ // value: 1.0,
270+ // suffix: this.suffix,
271+ // }
272+ // }
273+
211274 /// Either sets the suffix of `self` to `rhs` or scales `rhs` if `self` has a value other than
212275 /// zero.
213276 ///
@@ -228,46 +291,54 @@ impl Quantity {
228291 }
229292}
230293
294+ trait FloatExt : PartialOrd + Sized {
295+ fn between ( self , start : Self , end : Self ) -> bool {
296+ self > start && self < end
297+ }
298+ }
299+
300+ impl FloatExt for f64 { }
301+
231302#[ cfg( test) ]
232303mod test {
233304 use super :: * ;
234305 use rstest:: rstest;
235306
307+ // See https://github.com/kubernetes/apimachinery/blob/3e8e52d6a1259ada73f63c1c7d1fad39d4ba9fb4/pkg/api/resource/quantity_test.go#L276-L287
308+ #[ rustfmt:: skip]
236309 #[ rstest]
237- #[ case( "49041204Ki" , Quantity { value: 49041204.0 , suffix: Suffix :: BinaryMultiple ( BinaryMultiple :: Kibi ) } ) ]
238- #[ case( "256Ki" , Quantity { value: 256.0 , suffix: Suffix :: BinaryMultiple ( BinaryMultiple :: Kibi ) } ) ]
239- #[ case( "1.5Gi" , Quantity { value: 1.5 , suffix: Suffix :: BinaryMultiple ( BinaryMultiple :: Gibi ) } ) ]
240- #[ case( "0.8Ti" , Quantity { value: 0.8 , suffix: Suffix :: BinaryMultiple ( BinaryMultiple :: Tebi ) } ) ]
241- #[ case( "3.2Pi" , Quantity { value: 3.2 , suffix: Suffix :: BinaryMultiple ( BinaryMultiple :: Pebi ) } ) ]
242- #[ case( "0.2Ei" , Quantity { value: 0.2 , suffix: Suffix :: BinaryMultiple ( BinaryMultiple :: Exbi ) } ) ]
243- #[ case( "8Mi" , Quantity { value: 8.0 , suffix: Suffix :: BinaryMultiple ( BinaryMultiple :: Mebi ) } ) ]
244- fn binary_quantity_from_str_pass ( #[ case] input : & str , #[ case] expected : Quantity ) {
310+ #[ case( "0" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Empty ) ) ]
311+ #[ case( "0n" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Nano ) ) ]
312+ #[ case( "0u" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Micro ) ) ]
313+ #[ case( "0m" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Milli ) ) ]
314+ #[ case( "0Ki" , 0.0 , Suffix :: BinaryMultiple ( BinaryMultiple :: Kibi ) ) ]
315+ #[ case( "0k" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Kilo ) ) ]
316+ #[ case( "0Mi" , 0.0 , Suffix :: BinaryMultiple ( BinaryMultiple :: Mebi ) ) ]
317+ #[ case( "0M" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Mega ) ) ]
318+ #[ case( "0Gi" , 0.0 , Suffix :: BinaryMultiple ( BinaryMultiple :: Gibi ) ) ]
319+ #[ case( "0G" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Giga ) ) ]
320+ #[ case( "0Ti" , 0.0 , Suffix :: BinaryMultiple ( BinaryMultiple :: Tebi ) ) ]
321+ #[ case( "0T" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Tera ) ) ]
322+ #[ case( "0Pi" , 0.0 , Suffix :: BinaryMultiple ( BinaryMultiple :: Pebi ) ) ]
323+ #[ case( "0P" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Peta ) ) ]
324+ #[ case( "0Ei" , 0.0 , Suffix :: BinaryMultiple ( BinaryMultiple :: Exbi ) ) ]
325+ #[ case( "0E" , 0.0 , Suffix :: DecimalMultiple ( DecimalMultiple :: Exa ) ) ]
326+ fn parse_zero_quantity ( #[ case] input : & str , #[ case] expected_value : f64 , #[ case] expected_suffix : Suffix ) {
245327 let parsed = Quantity :: from_str ( input) . unwrap ( ) ;
246- assert_eq ! ( parsed, expected) ;
247- }
248328
249- #[ rstest]
250- #[ case( "49041204k" , Quantity { value: 49041204.0 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Kilo ) } ) ]
251- #[ case( "256k" , Quantity { value: 256.0 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Kilo ) } ) ]
252- #[ case( "1.5G" , Quantity { value: 1.5 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Giga ) } ) ]
253- #[ case( "0.8T" , Quantity { value: 0.8 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Tera ) } ) ]
254- #[ case( "3.2P" , Quantity { value: 3.2 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Peta ) } ) ]
255- #[ case( "0.2E" , Quantity { value: 0.2 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Exa ) } ) ]
256- #[ case( "4m" , Quantity { value: 4.0 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Milli ) } ) ]
257- #[ case( "8M" , Quantity { value: 8.0 , suffix: Suffix :: DecimalMultiple ( DecimalMultiple :: Mega ) } ) ]
258- fn decimal_quantity_from_str_pass ( #[ case] input : & str , #[ case] expected : Quantity ) {
259- let parsed = Quantity :: from_str ( input) . unwrap ( ) ;
260- assert_eq ! ( parsed, expected) ;
329+ assert_eq ! ( parsed. suffix, expected_suffix) ;
330+ assert_eq ! ( parsed. value, expected_value) ;
261331 }
262332
333+ // See https://github.com/kubernetes/apimachinery/blob/3e8e52d6a1259ada73f63c1c7d1fad39d4ba9fb4/pkg/api/resource/quantity_test.go#L289
334+ #[ rustfmt:: skip]
263335 #[ rstest]
264- #[ case( "1.234e-3.21" , Quantity { value: 1.234 , suffix: Suffix :: DecimalExponent ( DecimalExponent :: from( -3.21 ) ) } ) ]
265- #[ case( "1.234E-3.21" , Quantity { value: 1.234 , suffix: Suffix :: DecimalExponent ( DecimalExponent :: from( -3.21 ) ) } ) ]
266- #[ case( "1.234e3" , Quantity { value: 1.234 , suffix: Suffix :: DecimalExponent ( DecimalExponent :: from( 3.0 ) ) } ) ]
267- #[ case( "1.234E3" , Quantity { value: 1.234 , suffix: Suffix :: DecimalExponent ( DecimalExponent :: from( 3.0 ) ) } ) ]
268- fn decimal_exponent_quantity_from_str_pass ( #[ case] input : & str , #[ case] expected : Quantity ) {
336+ #[ case( "12.34" , 12.34 ) ]
337+ #[ case( "12" , 12.0 ) ]
338+ #[ case( "1" , 1.0 ) ]
339+ fn parse_quantity_without_suffix ( #[ case] input : & str , #[ case] expected_value : f64 ) {
269340 let parsed = Quantity :: from_str ( input) . unwrap ( ) ;
270- assert_eq ! ( parsed, expected ) ;
341+ assert_eq ! ( parsed. value , expected_value ) ;
271342 }
272343
273344 #[ rstest]
0 commit comments