From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox 4/5] pve-api-types: add proxmox-fixed-string
Date: Thu, 20 Nov 2025 15:50:12 +0100 [thread overview]
Message-ID: <20251120145031.550340-5-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20251120145031.550340-1-s.hanreich@proxmox.com>
The FixedString type has been moved into its own crate, so it can be
shared with pbs-api-types. Delete the existing implementation and use
the proxmox-fixed-string crate instead. No functional changes
intended.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
pve-api-types/Cargo.toml | 1 +
pve-api-types/debian/control | 2 +
pve-api-types/src/types/fixed_string.rs | 274 ------------------------
pve-api-types/src/types/mod.rs | 3 +-
4 files changed, 4 insertions(+), 276 deletions(-)
delete mode 100644 pve-api-types/src/types/fixed_string.rs
diff --git a/pve-api-types/Cargo.toml b/pve-api-types/Cargo.toml
index 0088777a..98671079 100644
--- a/pve-api-types/Cargo.toml
+++ b/pve-api-types/Cargo.toml
@@ -22,6 +22,7 @@ serde_json.workspace = true
serde_plain.workspace = true
#
proxmox-api-macro.workspace = true
+proxmox-fixed-string.workspace = true
proxmox-serde = { workspace = true, features = [ "perl" ] }
proxmox-schema = { workspace = true, features = [ "api-types", "api-macro" ] }
diff --git a/pve-api-types/debian/control b/pve-api-types/debian/control
index 5e9cdbc1..ca45b5ab 100644
--- a/pve-api-types/debian/control
+++ b/pve-api-types/debian/control
@@ -9,6 +9,7 @@ Build-Depends-Arch: cargo:native <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-async-trait-0.1+default-dev <!nocheck>,
librust-proxmox-api-macro-1+default-dev (>= 1.4.3-~~) <!nocheck>,
+ librust-proxmox-fixed-string-0.1+default-dev <!nocheck>,
librust-proxmox-schema-5+api-macro-dev (>= 5.0.1-~~) <!nocheck>,
librust-proxmox-schema-5+api-types-dev (>= 5.0.1-~~) <!nocheck>,
librust-proxmox-schema-5+default-dev (>= 5.0.1-~~) <!nocheck>,
@@ -35,6 +36,7 @@ Depends:
librust-anyhow-1+default-dev,
librust-async-trait-0.1+default-dev,
librust-proxmox-api-macro-1+default-dev (>= 1.4.3-~~),
+ librust-proxmox-fixed-string-0.1+default-dev,
librust-proxmox-schema-5+api-macro-dev (>= 5.0.1-~~),
librust-proxmox-schema-5+api-types-dev (>= 5.0.1-~~),
librust-proxmox-schema-5+default-dev (>= 5.0.1-~~),
diff --git a/pve-api-types/src/types/fixed_string.rs b/pve-api-types/src/types/fixed_string.rs
deleted file mode 100644
index e70e1327..00000000
--- a/pve-api-types/src/types/fixed_string.rs
+++ /dev/null
@@ -1,274 +0,0 @@
-use std::borrow::Borrow;
-use std::cmp::Ordering;
-use std::error::Error;
-use std::fmt;
-use std::ops::Deref;
-use std::str::FromStr;
-
-use serde::{Deserialize, Serialize};
-
-/// Error type used by constructors of [`FixedString`]
-#[derive(Clone, Copy, Debug)]
-pub struct TooLongError;
-
-impl Error for TooLongError {}
-
-impl fmt::Display for TooLongError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- f.write_str("string is longer than 23 characters")
- }
-}
-
-/// An immutable string type with a maximum size of 23 bytes.
-///
-/// After construction it is guaranteed that its contents are:
-/// * valid utf-8
-/// * not longer than 23 characters
-///
-/// FixedString is immutable, therefore it is sufficient to validate the invariants only at
-/// construction time to guarantee that they will always hold during the lifecycle of the
-/// struct.
-#[derive(Clone, Copy)]
-pub struct FixedString {
- buf: [u8; 23],
- len: u8,
-}
-
-impl FixedString {
- /// Creates a new FixedString instance from a str reference.
- ///
- /// # Errors
- /// This function will return an error if:
- /// * The passed string is longer than 23 bytes
- pub fn new(value: &str) -> Result<Self, TooLongError> {
- if value.len() > 23 {
- return Err(TooLongError);
- }
-
- let mut buf = [0; 23];
- buf[..value.len()].copy_from_slice(value.as_bytes());
-
- Ok(Self {
- buf,
- // SAFETY: self.len is at least 0 and at most 23, which fits into u8
- len: value.len() as u8,
- })
- }
-
- /// Returns a str reference to the stored data
- #[inline]
- pub fn as_str(&self) -> &str {
- // SAFETY: self.buf must be a valid utf-8 string by construction
- unsafe { str::from_utf8_unchecked(self.as_bytes()) }
- }
-
- /// Returns a reference to the set bytes in the stored buffer
- #[inline]
- pub fn as_bytes(&self) -> &[u8] {
- // SAFETY: self.len >= 0 and self.len <= 23 by construction
- unsafe { self.buf.get_unchecked(..self.len as usize) }
- }
-}
-
-macro_rules! forward_impl_to_bytes {
- ($($trait:ident {$fn:ident -> $out:ty })+) => {
- $(
- impl $trait for FixedString {
- #[inline]
- fn $fn(&self, other: &Self) -> $out {
- <[u8] as $trait>::$fn(self.as_bytes(), other.as_bytes())
- }
- }
- )+
- };
-}
-
-forward_impl_to_bytes! {
- PartialEq { eq -> bool }
- PartialOrd { partial_cmp -> Option<Ordering> }
- Ord { cmp -> Ordering }
-}
-
-macro_rules! forward_impl_to_str_bidir {
- ($($trait:ident {$fn:ident -> $out:ty })+) => {
- $(
- impl $trait<str> for FixedString {
- #[inline]
- fn $fn(&self, other: &str) -> $out {
- <str as $trait>::$fn(self.as_str(), other)
- }
- }
-
- impl $trait<FixedString> for &str {
- #[inline]
- fn $fn(&self, other: &FixedString) -> $out {
- <str as $trait>::$fn(self, other.as_str())
- }
- }
- )+
- };
-}
-
-forward_impl_to_str_bidir! {
- PartialEq { eq -> bool }
- PartialOrd { partial_cmp -> Option<Ordering> }
-}
-
-impl Eq for FixedString {}
-
-impl fmt::Display for FixedString {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- fmt::Display::fmt(self.as_str(), f)
- }
-}
-
-impl fmt::Debug for FixedString {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- fmt::Display::fmt(self.as_str(), f)
- }
-}
-
-impl Deref for FixedString {
- type Target = str;
-
- fn deref(&self) -> &str {
- self.as_str()
- }
-}
-
-impl AsRef<str> for FixedString {
- fn as_ref(&self) -> &str {
- self.as_str()
- }
-}
-
-impl AsRef<[u8]> for FixedString {
- fn as_ref(&self) -> &[u8] {
- self.as_bytes()
- }
-}
-
-impl Borrow<str> for FixedString {
- fn borrow(&self) -> &str {
- self.as_str()
- }
-}
-
-impl TryFrom<String> for FixedString {
- type Error = TooLongError;
-
- fn try_from(value: String) -> Result<Self, Self::Error> {
- FixedString::new(value.as_str())
- }
-}
-
-impl FromStr for FixedString {
- type Err = TooLongError;
-
- fn from_str(value: &str) -> Result<Self, Self::Err> {
- FixedString::new(value)
- }
-}
-
-impl TryFrom<&str> for FixedString {
- type Error = TooLongError;
-
- fn try_from(value: &str) -> Result<Self, Self::Error> {
- FixedString::new(value)
- }
-}
-
-impl Serialize for FixedString {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- serializer.serialize_str(self.as_str())
- }
-}
-
-impl<'de> Deserialize<'de> for FixedString {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- struct FixedStringVisitor;
-
- impl<'de> serde::de::Visitor<'de> for FixedStringVisitor {
- type Value = FixedString;
-
- fn expecting(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
- f.write_str("a string that is at most 23 bytes long")
- }
-
- fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
- where
- E: serde::de::Error,
- {
- v.try_into().map_err(E::custom)
- }
- }
-
- deserializer.deserialize_str(FixedStringVisitor)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use serde_plain;
-
- #[test]
- fn test_construct() {
- let fixed_string = FixedString::new("").expect("empty string is valid");
- assert_eq!("", fixed_string);
-
- let fixed_string = FixedString::new("a").expect("valid string");
- assert_eq!("a", fixed_string);
-
- let fixed_string = FixedString::new("🌏🌏🌏🌏🌏").expect("valid string");
- assert_eq!("🌏🌏🌏🌏🌏", fixed_string);
-
- let fixed_string =
- FixedString::new("aaaaaaaaaaaaaaaaaaaaaaa").expect("23 characters are allowed");
- assert_eq!("aaaaaaaaaaaaaaaaaaaaaaa", fixed_string);
-
- FixedString::new("🌏🌏🌏🌏🌏🌏").expect_err("string too long");
- FixedString::new("aaaaaaaaaaaaaaaaaaaaaaaa").expect_err("string too long");
- }
-
- #[test]
- fn test_serialize_deserialize() {
- let valid_string = "aaaaaaaaaaaaaaaaaaaaaaa";
-
- let fixed_string: FixedString =
- serde_plain::from_str("aaaaaaaaaaaaaaaaaaaaaaa").expect("deserialization works");
- assert_eq!(valid_string, fixed_string);
-
- let serialized_string =
- serde_plain::to_string(&fixed_string).expect("can be serialized into a string");
- assert_eq!(valid_string, serialized_string);
-
- serde_plain::from_str::<FixedString>("aaaaaaaaaaaaaaaaaaaaaaaa")
- .expect_err("cannot deserialize string that is too long");
- }
-
- #[test]
- fn test_ord() {
- let fixed_string = FixedString::new("abc").expect("valid string");
-
- assert!(fixed_string == fixed_string);
- assert!(fixed_string >= fixed_string);
- assert!(fixed_string <= fixed_string);
-
- assert!("ab" < fixed_string);
- assert!("abc" == fixed_string);
- assert!("abcd" > fixed_string);
-
- let larger_fixed_string = FixedString::new("abcde").expect("valid string");
-
- assert!(larger_fixed_string > fixed_string);
- assert!(fixed_string < larger_fixed_string);
- }
-}
diff --git a/pve-api-types/src/types/mod.rs b/pve-api-types/src/types/mod.rs
index 63209d84..e5eed817 100644
--- a/pve-api-types/src/types/mod.rs
+++ b/pve-api-types/src/types/mod.rs
@@ -17,8 +17,7 @@ pub mod array;
pub mod stringlist;
pub mod verifiers;
-mod fixed_string;
-pub use fixed_string::{FixedString, TooLongError};
+use proxmox_fixed_string::FixedString;
include!("../generated/types.rs");
--
2.47.3
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
next prev parent reply other threads:[~2025-11-20 14:51 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-20 14:50 [pbs-devel] [RFC proxmox{, -datacenter-manager} 0/6] Add fallback variants to PBS API types Stefan Hanreich
2025-11-20 14:50 ` [pbs-devel] [PATCH proxmox 1/5] proxmox-upgrade-checks: fix meta package version check Stefan Hanreich
2025-11-20 15:04 ` Shannon Sterz
2025-11-20 15:08 ` Stefan Hanreich
2025-11-20 14:50 ` [pbs-devel] [PATCH proxmox 2/5] proxmox-fixed-string: extract FixedString into own crate Stefan Hanreich
2025-11-20 14:50 ` [pbs-devel] [PATCH proxmox 3/5] proxmox-fixed-string: implement hash trait Stefan Hanreich
2025-11-20 14:50 ` Stefan Hanreich [this message]
2025-11-20 14:50 ` [pbs-devel] [PATCH proxmox 5/5] pbs-api-types: add fallback variants to enums in public API Stefan Hanreich
2025-11-20 14:50 ` [pbs-devel] [PATCH proxmox-datacenter-manager 1/1] tree-wide: add enum fallback variants for pbs api types Stefan Hanreich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251120145031.550340-5-s.hanreich@proxmox.com \
--to=s.hanreich@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox