From f53a0d608590561e099b62731bfab91add951473 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 10 Jun 2026 01:02:00 +0800 Subject: [PATCH] feat: impl serde Signed-off-by: tison --- Cargo.lock | 143 +++++++++++++++++++++++++++++++++++++++++++ bsize/Cargo.toml | 9 +++ bsize/src/lib.rs | 3 + bsize/src/parse.rs | 13 ++-- bsize/src/serde.rs | 149 +++++++++++++++++++++++++++++++++++++++++++++ bsize/src/types.rs | 2 +- 6 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 bsize/src/serde.rs diff --git a/Cargo.lock b/Cargo.lock index 85e901d..2912c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,12 @@ dependencies = [ [[package]] name = "bsize" version = "0.1.0-rc.1" +dependencies = [ + "serde", + "serde_core", + "serde_json", + "toml", +] [[package]] name = "clap" @@ -102,24 +108,58 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + [[package]] name = "once_cell_polyfill" version = "1.70.2" @@ -144,6 +184,58 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "strsim" version = "0.11.1" @@ -161,6 +253,45 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -197,6 +328,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + [[package]] name = "x" version = "0.0.0" @@ -204,3 +341,9 @@ dependencies = [ "clap", "which", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/bsize/Cargo.toml b/bsize/Cargo.toml index e3442f6..19049b3 100644 --- a/bsize/Cargo.toml +++ b/bsize/Cargo.toml @@ -30,6 +30,15 @@ rust-version.workspace = true [features] default = [] +serde = ["dep:serde_core"] + +[dependencies] +serde_core = { version = "1", optional = true } + +[dev-dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1" } +toml = { version = "1.1" } [lints] workspace = true diff --git a/bsize/src/lib.rs b/bsize/src/lib.rs index 28b1c42..b01c035 100644 --- a/bsize/src/lib.rs +++ b/bsize/src/lib.rs @@ -17,10 +17,13 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![deny(missing_docs)] #![no_std] +extern crate alloc; mod display; mod ops; mod parse; +#[cfg(feature = "serde")] +mod serde; mod types; mod unsigned; diff --git a/bsize/src/parse.rs b/bsize/src/parse.rs index 05e3a1c..27c6cc5 100644 --- a/bsize/src/parse.rs +++ b/bsize/src/parse.rs @@ -199,14 +199,17 @@ fn parse_size(mut src: &[u8]) -> Result { #[cfg(test)] mod tests { + use alloc::string::ToString; + use super::*; fn assert_parse_ok(input: &str, expected: u64) { - assert_eq!( - input.parse::>(), - Ok(BSize::b(expected)), - "input: {input:?}", - ); + let actual = BSize::::from_str(input).unwrap(); + let expected = BSize::(expected); + assert_eq!(actual, expected, "input: {input:?}"); + + let round_trip = actual.to_string().parse::>().unwrap(); + assert_eq!(round_trip, expected, "input: {input:?}"); } fn assert_parse_err(input: &str, expected: ParseError) { diff --git a/bsize/src/serde.rs b/bsize/src/serde.rs new file mode 100644 index 0000000..1034c3c --- /dev/null +++ b/bsize/src/serde.rs @@ -0,0 +1,149 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::fmt; + +use serde_core::de; + +use crate::BSize; + +macro_rules! impl_serialize { + ($($ty:ty),* $(,)?) => { + $( + impl serde_core::Serialize for BSize<$ty> { + fn serialize(&self, ser: S) -> Result + where + S: serde_core::Serializer, + { + if ser.is_human_readable() { + ser.collect_str(self) + } else { + self.0.serialize(ser) + } + } + } + )* + }; +} + +impl_serialize!(u8, u16, u32, u64, usize); + +macro_rules! impl_deserialize { + ($($ty:ty),* $(,)?) => { + $( + impl<'de> serde_core::Deserialize<'de> for BSize<$ty> { + fn deserialize(de: D) -> Result + where + D: serde_core::Deserializer<'de>, + { + struct Visitor; + + impl de::Visitor<'_> for Visitor { + type Value = BSize<$ty>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an integer or string") + } + + fn visit_i64(self, value: i64) -> Result { + if let Ok(val) = u64::try_from(value) { + if val <= <$ty>::MAX as u64 { + Ok(BSize(val as $ty)) + } else { + Err(E::invalid_value( + de::Unexpected::Signed(value), + &"integer overflow", + )) + } + } else { + Err(E::invalid_value( + de::Unexpected::Signed(value), + &"integer overflow", + )) + } + } + + fn visit_u64(self, value: u64) -> Result { + if value <= <$ty>::MAX as u64 { + Ok(BSize(value as $ty)) + } else { + Err(E::invalid_value( + de::Unexpected::Unsigned(value), + &"integer overflow", + )) + } + } + + fn visit_str(self, value: &str) -> Result { + if let Ok(val) = value.parse() { + Ok(val) + } else { + Err(E::invalid_value( + de::Unexpected::Str(value), + &"parsable string", + )) + } + } + } + + if de.is_human_readable() { + de.deserialize_any(Visitor) + } else { + de.deserialize_u64(Visitor) + } + } + } + )* + }; +} + +impl_deserialize!(u8, u16, u32, u64, usize); + +#[cfg(test)] +mod tests { + use serde::Deserialize; + use serde::Serialize; + + use super::*; + + #[test] + fn test_serde() { + #[derive(Serialize, Deserialize)] + struct S { + x: BSize, + } + + let s = serde_json::from_str::(r#"{ "x": "5 B" }"#).unwrap(); + assert_eq!(s.x, BSize::(5)); + + let s = serde_json::from_str::(r#"{ "x": 1048576 }"#).unwrap(); + assert_eq!(s.x, "1 MiB".parse::>().unwrap()); + + let s = toml::from_str::(r#"x = "2.5 MiB""#).unwrap(); + assert_eq!(s.x, "2.5 MiB".parse::>().unwrap()); + + // i64 MAX + let s = toml::from_str::(r#"x = "9223372036854775807""#).unwrap(); + assert_eq!(s.x, "9223372036854775807".parse::>().unwrap()); + } + + #[test] + fn test_serde_json() { + let json = serde_json::to_string(&BSize::::mib(1)).unwrap(); + assert_eq!(json, "\"1048576 B\""); + + let deserialized = serde_json::from_str::>(&json).unwrap(); + assert_eq!(deserialized.0, 1048576); + } +} diff --git a/bsize/src/types.rs b/bsize/src/types.rs index 1795194..68b3a74 100644 --- a/bsize/src/types.rs +++ b/bsize/src/types.rs @@ -29,7 +29,7 @@ impl fmt::Debug for BSize { impl fmt::Display for BSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} bytes", self.0) + write!(f, "{} B", self.0) } }