Skip to content

Commit 6bbf707

Browse files
authored
feat: Add Serde impl for CpuQuantity and MemoryQuantity (#724)
* Add Serde impl for CpuQuantity * Add Serde impl for MemoryQuantity * Adjust test names * Update changelog * Fix typo
1 parent 407cfec commit 6bbf707

File tree

3 files changed

+205
-35
lines changed

3 files changed

+205
-35
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Add Serde `Deserialize` and `Serialize` support for `CpuQuantity` and `MemoryQuantity` ([#724]).
10+
11+
[#724]: https://github.com/stackabletech/operator-rs/pull/724
12+
713
## [0.62.0] - 2024-01-19
814

915
### Added

src/cpu.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::{
2+
fmt::Display,
23
iter::Sum,
34
ops::{Add, AddAssign, Div, Mul, MulAssign},
45
str::FromStr,
56
};
67

78
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
9+
use serde::{de::Visitor, Deserialize, Serialize};
810

911
use crate::error::{Error, OperatorResult};
1012

@@ -33,6 +35,50 @@ impl CpuQuantity {
3335
}
3436
}
3537

38+
impl Serialize for CpuQuantity {
39+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
40+
where
41+
S: serde::Serializer,
42+
{
43+
serializer.serialize_str(&self.to_string())
44+
}
45+
}
46+
47+
impl<'de> Deserialize<'de> for CpuQuantity {
48+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
49+
where
50+
D: serde::Deserializer<'de>,
51+
{
52+
struct CpuQuantityVisitor;
53+
54+
impl<'de> Visitor<'de> for CpuQuantityVisitor {
55+
type Value = CpuQuantity;
56+
57+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
58+
formatter.write_str("a valid CPU quantity")
59+
}
60+
61+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
62+
where
63+
E: serde::de::Error,
64+
{
65+
CpuQuantity::from_str(v).map_err(serde::de::Error::custom)
66+
}
67+
}
68+
69+
deserializer.deserialize_str(CpuQuantityVisitor)
70+
}
71+
}
72+
73+
impl Display for CpuQuantity {
74+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75+
match self.millis < 1000 {
76+
true => write!(f, "{}m", self.millis),
77+
false => write!(f, "{}", self.as_cpu_count()),
78+
}
79+
}
80+
}
81+
3682
impl FromStr for CpuQuantity {
3783
type Err = Error;
3884

@@ -185,4 +231,54 @@ mod test {
185231
let result = CpuQuantity::from_str(s);
186232
assert!(result.is_err());
187233
}
234+
235+
#[rstest]
236+
#[case(CpuQuantity::from_millis(10000), "10")]
237+
#[case(CpuQuantity::from_millis(1500), "1.5")]
238+
#[case(CpuQuantity::from_millis(999), "999m")]
239+
#[case(CpuQuantity::from_millis(500), "500m")]
240+
#[case(CpuQuantity::from_millis(100), "100m")]
241+
#[case(CpuQuantity::from_millis(2000), "2")]
242+
#[case(CpuQuantity::from_millis(1000), "1")]
243+
fn test_display_to_string(#[case] cpu: CpuQuantity, #[case] expected: &str) {
244+
assert_eq!(cpu.to_string(), expected)
245+
}
246+
247+
#[rstest]
248+
#[case(CpuQuantity::from_millis(10000), "cpu: '10'\n")]
249+
#[case(CpuQuantity::from_millis(1500), "cpu: '1.5'\n")]
250+
#[case(CpuQuantity::from_millis(999), "cpu: 999m\n")]
251+
#[case(CpuQuantity::from_millis(500), "cpu: 500m\n")]
252+
#[case(CpuQuantity::from_millis(100), "cpu: 100m\n")]
253+
#[case(CpuQuantity::from_millis(2000), "cpu: '2'\n")]
254+
#[case(CpuQuantity::from_millis(1000), "cpu: '1'\n")]
255+
fn test_serialize(#[case] cpu: CpuQuantity, #[case] expected: &str) {
256+
#[derive(Serialize)]
257+
struct Cpu {
258+
cpu: CpuQuantity,
259+
}
260+
261+
let cpu = Cpu { cpu };
262+
let output = serde_yaml::to_string(&cpu).unwrap();
263+
264+
assert_eq!(output, expected);
265+
}
266+
267+
#[rstest]
268+
#[case("cpu: '10'", CpuQuantity::from_millis(10000))]
269+
#[case("cpu: '1.5'", CpuQuantity::from_millis(1500))]
270+
#[case("cpu: 999m", CpuQuantity::from_millis(999))]
271+
#[case("cpu: 500m", CpuQuantity::from_millis(500))]
272+
#[case("cpu: 100m", CpuQuantity::from_millis(100))]
273+
#[case("cpu: 2", CpuQuantity::from_millis(2000))]
274+
#[case("cpu: 1", CpuQuantity::from_millis(1000))]
275+
fn test_deserialize(#[case] input: &str, #[case] expected: CpuQuantity) {
276+
#[derive(Deserialize)]
277+
struct Cpu {
278+
cpu: CpuQuantity,
279+
}
280+
281+
let cpu: Cpu = serde_yaml::from_str(input).unwrap();
282+
assert_eq!(cpu.cpu, expected);
283+
}
188284
}

src/memory.rs

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//! For details on Kubernetes quantities see: <https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go>
1010
1111
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
12+
use serde::{de::Visitor, Deserialize, Serialize};
1213

1314
use crate::error::{Error, OperatorResult};
1415
use std::{
@@ -301,6 +302,66 @@ impl MemoryQuantity {
301302
}
302303
}
303304

305+
impl Serialize for MemoryQuantity {
306+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
307+
where
308+
S: serde::Serializer,
309+
{
310+
serializer.serialize_str(&self.to_string())
311+
}
312+
}
313+
314+
impl<'de> Deserialize<'de> for MemoryQuantity {
315+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
316+
where
317+
D: serde::Deserializer<'de>,
318+
{
319+
struct MemoryQuantityVisitor;
320+
321+
impl<'de> Visitor<'de> for MemoryQuantityVisitor {
322+
type Value = MemoryQuantity;
323+
324+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
325+
formatter.write_str("a valid memory quantity")
326+
}
327+
328+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
329+
where
330+
E: serde::de::Error,
331+
{
332+
MemoryQuantity::from_str(v).map_err(serde::de::Error::custom)
333+
}
334+
}
335+
336+
deserializer.deserialize_str(MemoryQuantityVisitor)
337+
}
338+
}
339+
340+
impl FromStr for MemoryQuantity {
341+
type Err = Error;
342+
343+
fn from_str(q: &str) -> OperatorResult<Self> {
344+
let start_of_unit =
345+
q.find(|c: char| c != '.' && !c.is_numeric())
346+
.ok_or(Error::NoQuantityUnit {
347+
value: q.to_owned(),
348+
})?;
349+
let (value, unit) = q.split_at(start_of_unit);
350+
Ok(MemoryQuantity {
351+
value: value.parse::<f32>().map_err(|_| Error::InvalidQuantity {
352+
value: q.to_owned(),
353+
})?,
354+
unit: unit.parse()?,
355+
})
356+
}
357+
}
358+
359+
impl Display for MemoryQuantity {
360+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361+
write!(f, "{}{}", self.value, self.unit)
362+
}
363+
}
364+
304365
impl Mul<f32> for MemoryQuantity {
305366
type Output = MemoryQuantity;
306367

@@ -395,31 +456,6 @@ impl PartialEq for MemoryQuantity {
395456

396457
impl Eq for MemoryQuantity {}
397458

398-
impl FromStr for MemoryQuantity {
399-
type Err = Error;
400-
401-
fn from_str(q: &str) -> OperatorResult<Self> {
402-
let start_of_unit =
403-
q.find(|c: char| c != '.' && !c.is_numeric())
404-
.ok_or(Error::NoQuantityUnit {
405-
value: q.to_owned(),
406-
})?;
407-
let (value, unit) = q.split_at(start_of_unit);
408-
Ok(MemoryQuantity {
409-
value: value.parse::<f32>().map_err(|_| Error::InvalidQuantity {
410-
value: q.to_owned(),
411-
})?,
412-
unit: unit.parse()?,
413-
})
414-
}
415-
}
416-
417-
impl Display for MemoryQuantity {
418-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419-
write!(f, "{}{}", self.value, self.unit)
420-
}
421-
}
422-
423459
impl TryFrom<Quantity> for MemoryQuantity {
424460
type Error = Error;
425461

@@ -474,7 +510,7 @@ mod test {
474510
#[case("1.2Gi")]
475511
#[case("1.6Gi")]
476512
#[case("1Gi")]
477-
pub fn test_fmt(#[case] q: String) {
513+
fn test_try_from_quantity(#[case] q: String) {
478514
let m = MemoryQuantity::try_from(Quantity(q.clone())).unwrap();
479515
let actual = format!("{m}");
480516
assert_eq!(q, actual);
@@ -486,7 +522,7 @@ mod test {
486522
#[case("2Mi", 0.8, "-Xmx1638k")]
487523
#[case("1.5Gi", 0.8, "-Xmx1229m")]
488524
#[case("2Gi", 0.8, "-Xmx1638m")]
489-
pub fn test_to_java_heap(#[case] q: &str, #[case] factor: f32, #[case] heap: &str) {
525+
fn test_to_java_heap(#[case] q: &str, #[case] factor: f32, #[case] heap: &str) {
490526
#[allow(deprecated)] // allow use of the deprecated 'to_java_heap' function to test it
491527
let actual = to_java_heap(&Quantity(q.to_owned()), factor).unwrap();
492528
assert_eq!(heap, actual);
@@ -498,7 +534,7 @@ mod test {
498534
#[case("1.2Gi", "1228m")]
499535
#[case("1.6Gi", "1638m")]
500536
#[case("1Gi", "1g")]
501-
pub fn test_format_java(#[case] q: String, #[case] expected: String) {
537+
fn test_format_java(#[case] q: String, #[case] expected: String) {
502538
let m = MemoryQuantity::try_from(Quantity(q)).unwrap();
503539
let actual = m.format_for_java().unwrap();
504540
assert_eq!(expected, actual);
@@ -513,7 +549,7 @@ mod test {
513549
#[case(2000f32, BinaryMultiple::Pebi, BinaryMultiple::Mebi, 2000f32*1024f32*1024f32*1024f32)]
514550
#[case(2000f32, BinaryMultiple::Pebi, BinaryMultiple::Kibi, 2000f32*1024f32*1024f32*1024f32*1024f32)]
515551
#[case(2000f32, BinaryMultiple::Exbi, BinaryMultiple::Pebi, 2000f32*1024f32)]
516-
pub fn test_scale_to(
552+
fn test_scale_to(
517553
#[case] value: f32,
518554
#[case] unit: BinaryMultiple,
519555
#[case] target_unit: BinaryMultiple,
@@ -537,7 +573,7 @@ mod test {
537573
#[case("2000Ki", 1.0, BinaryMultiple::Mebi, 1)]
538574
#[case("4000Mi", 1.0, BinaryMultiple::Gibi, 3)]
539575
#[case("4000Mi", 0.8, BinaryMultiple::Gibi, 3)]
540-
pub fn test_to_java_heap_value(
576+
fn test_to_java_heap_value(
541577
#[case] q: &str,
542578
#[case] factor: f32,
543579
#[case] target_unit: BinaryMultiple,
@@ -555,7 +591,7 @@ mod test {
555591
#[case("1000Mi", 1.0, BinaryMultiple::Gibi)]
556592
#[case("1023Mi", 1.0, BinaryMultiple::Gibi)]
557593
#[case("1024Mi", 0.8, BinaryMultiple::Gibi)]
558-
pub fn test_to_java_heap_value_failure(
594+
fn test_to_java_heap_value_failure(
559595
#[case] q: &str,
560596
#[case] factor: f32,
561597
#[case] target_unit: BinaryMultiple,
@@ -570,7 +606,7 @@ mod test {
570606
#[case("1Mi", "512Ki", "512Ki")]
571607
#[case("2Mi", "512Ki", "1536Ki")]
572608
#[case("2048Ki", "1Mi", "1024Ki")]
573-
pub fn test_subtraction(#[case] lhs: &str, #[case] rhs: &str, #[case] res: &str) {
609+
fn test_subtraction(#[case] lhs: &str, #[case] rhs: &str, #[case] res: &str) {
574610
let lhs = MemoryQuantity::try_from(Quantity(lhs.to_owned())).unwrap();
575611
let rhs = MemoryQuantity::try_from(Quantity(rhs.to_owned())).unwrap();
576612
let expected = MemoryQuantity::try_from(Quantity(res.to_owned())).unwrap();
@@ -587,7 +623,7 @@ mod test {
587623
#[case("1Mi", "512Ki", "1536Ki")]
588624
#[case("2Mi", "512Ki", "2560Ki")]
589625
#[case("2048Ki", "1Mi", "3072Ki")]
590-
pub fn test_addition(#[case] lhs: &str, #[case] rhs: &str, #[case] res: &str) {
626+
fn test_addition(#[case] lhs: &str, #[case] rhs: &str, #[case] res: &str) {
591627
let lhs = MemoryQuantity::try_from(Quantity(lhs.to_owned())).unwrap();
592628
let rhs = MemoryQuantity::try_from(Quantity(rhs.to_owned())).unwrap();
593629
let expected = MemoryQuantity::try_from(Quantity(res.to_owned())).unwrap();
@@ -608,7 +644,7 @@ mod test {
608644
#[case("100Ki", "101Ki", false)]
609645
#[case("1Mi", "100Ki", true)]
610646
#[case("2000Ki", "1Mi", true)]
611-
pub fn test_comparison(#[case] lhs: &str, #[case] rhs: &str, #[case] res: bool) {
647+
fn test_comparison(#[case] lhs: &str, #[case] rhs: &str, #[case] res: bool) {
612648
let lhs = MemoryQuantity::try_from(Quantity(lhs.to_owned())).unwrap();
613649
let rhs = MemoryQuantity::try_from(Quantity(rhs.to_owned())).unwrap();
614650
assert_eq!(lhs > rhs, res)
@@ -619,9 +655,41 @@ mod test {
619655
#[case("100Ki", "200Ki", false)]
620656
#[case("1Mi", "1024Ki", true)]
621657
#[case("1024Ki", "1Mi", true)]
622-
pub fn test_eq(#[case] lhs: &str, #[case] rhs: &str, #[case] res: bool) {
658+
fn test_eq(#[case] lhs: &str, #[case] rhs: &str, #[case] res: bool) {
623659
let lhs = MemoryQuantity::try_from(Quantity(lhs.to_owned())).unwrap();
624660
let rhs = MemoryQuantity::try_from(Quantity(rhs.to_owned())).unwrap();
625661
assert_eq!(lhs == rhs, res)
626662
}
663+
664+
#[rstest]
665+
#[case(MemoryQuantity::from_mebi(1536.0), "memory: 1536Mi\n")]
666+
#[case(MemoryQuantity::from_mebi(100.0), "memory: 100Mi\n")]
667+
#[case(MemoryQuantity::from_gibi(10.0), "memory: 10Gi\n")]
668+
#[case(MemoryQuantity::from_gibi(1.0), "memory: 1Gi\n")]
669+
fn test_serialize(#[case] memory: MemoryQuantity, #[case] expected: &str) {
670+
#[derive(Serialize)]
671+
struct Memory {
672+
memory: MemoryQuantity,
673+
}
674+
675+
let memory = Memory { memory };
676+
let output = serde_yaml::to_string(&memory).unwrap();
677+
678+
assert_eq!(output, expected);
679+
}
680+
681+
#[rstest]
682+
#[case("memory: 1536Mi", MemoryQuantity::from_mebi(1536.0))]
683+
#[case("memory: 100Mi", MemoryQuantity::from_mebi(100.0))]
684+
#[case("memory: 10Gi", MemoryQuantity::from_gibi(10.0))]
685+
#[case("memory: 1Gi", MemoryQuantity::from_gibi(1.0))]
686+
fn test_deserialize(#[case] input: &str, #[case] expected: MemoryQuantity) {
687+
#[derive(Deserialize)]
688+
struct Memory {
689+
memory: MemoryQuantity,
690+
}
691+
692+
let memory: Memory = serde_yaml::from_str(input).unwrap();
693+
assert_eq!(memory.memory, expected);
694+
}
627695
}

0 commit comments

Comments
 (0)