From: Shannon Sterz <s.sterz@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [PATCH proxmox v2 2/5] pve-api-types: generate array objects
Date: Thu, 13 Nov 2025 13:00:14 +0100 [thread overview]
Message-ID: <20251113120021.331639-3-s.sterz@proxmox.com> (raw)
In-Reply-To: <20251113120021.331639-1-s.sterz@proxmox.com>
From: Wolfgang Bumiller <w.bumiller@proxmox.com>
Previously the array objects had their *item* as schema, which is
wrong.
We now generate the object schema by pointing to the item schemas for
each field.
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
[SS: minor clean up in commit message]
Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
pve-api-types/src/types/macros.rs | 148 +++++++++++++++++++++++++++++-
pve-api-types/src/types/mod.rs | 3 +
2 files changed, 149 insertions(+), 2 deletions(-)
diff --git a/pve-api-types/src/types/macros.rs b/pve-api-types/src/types/macros.rs
index 32a04b66..e6116782 100644
--- a/pve-api-types/src/types/macros.rs
+++ b/pve-api-types/src/types/macros.rs
@@ -1,3 +1,124 @@
+use proxmox_schema::SchemaPropertyEntry;
+
+pub(crate) const __DIGIT_SPACE: usize = 4;
+
+/// Since our object schemas need lexicographically sorted names, we generate *those* indices
+/// separately.
+///
+/// The idea is as follows:
+/// - If we can attach a zero without going out of bounds, that's the next number.
+/// (1 => 10 => 100 => 1000, and so on)
+/// - Otherwise, repeat until we end up at zero (which is the end):
+/// - If the number does not end in a `9`, we can just increment. If we don't exceed the limit,
+/// return the number.
+/// (3 => 4, 134 => 135, 3850 => 3851, and so on)
+///
+/// - If it does end with a `9`, cut it off:
+/// (1299 => 129 => 12, 14399 => 1439 => 143)
+const fn next_lexicographical_number(mut at: usize, count: usize) -> Option<usize> {
+ // special case since `0 * 10` is still 0 ;-)
+ if at == 0 {
+ return Some(1);
+ }
+
+ let longer = at * 10;
+ if longer < count {
+ return Some(longer);
+ }
+
+ while at != 0 {
+ if at % 10 != 9 {
+ at += 1;
+ if at < count {
+ return Some(at);
+ }
+ }
+ at /= 10;
+ }
+
+ None
+}
+
+/// Equivalent to `write!(to, "{name}{index}")`.
+pub(crate) const fn write_name_index(to: &mut [u8], name: &'static str, mut index: usize) {
+ let name = name.as_bytes();
+ let mut len = 0;
+ while len != name.len() {
+ to[len] = name[len];
+ len += 1;
+ }
+ if index == 0 {
+ to[len] = b'0';
+ len += 1;
+ } else {
+ let mut digits = 0;
+ let mut copy = index;
+ while copy != 0 {
+ digits += 1;
+ copy /= 10;
+ }
+ len += digits;
+
+ let mut at = len - 1;
+ while index != 0 {
+ to[at] = b'0' + (index % 10) as u8;
+ index /= 10;
+ at -= 1;
+ }
+ }
+}
+
+/// Fill the buffer in `data` with `prefix0`, `prefix1`, `prefix2`, ... - but sorted
+/// lexicographically!
+pub(crate) const fn __fill_names<const N: usize>(prefix: &'static str, data: &mut [u8]) {
+ let unit_size = __DIGIT_SPACE + prefix.len();
+
+ let mut item = 0;
+ let mut sorted_index = Some(0);
+ while item != N {
+ let at = item * unit_size;
+
+ let (_, slot) = data.split_at_mut(at);
+ match sorted_index {
+ None => panic!("ran out of indices"),
+ Some(index) => {
+ write_name_index(slot, prefix, index);
+ sorted_index = next_lexicographical_number(index, N);
+ }
+ }
+
+ item += 1;
+ }
+}
+
+/// Assuming `data` is now an array of field names, perform the equivalent of:
+///
+/// `properties[N].0 = fields[N] foreach N;`
+pub(crate) const fn __fill_properties<const N: usize>(
+ prefix: &'static str,
+ mut data: &'static [u8],
+ properties: &mut [SchemaPropertyEntry; N],
+) {
+ let unit_size = __DIGIT_SPACE + prefix.len();
+ let mut item = 0;
+ while item != N {
+ let slot;
+ (slot, data) = data.split_at(unit_size);
+ let mut len = 0;
+ while len != unit_size && slot[len] != 0 {
+ len += 1;
+ }
+ let slot = slot.split_at(len).0;
+
+ match std::str::from_utf8(slot) {
+ Ok(field_name) => properties[item].0 = field_name,
+ Err(_) => panic!("non utf-8 field"),
+ }
+
+ item += 1;
+ }
+}
+
macro_rules! generate_array_field {
($type_name:ident [ $array_len:expr ] :
$doc:expr,
@@ -12,11 +133,34 @@ macro_rules! generate_array_field {
impl $type_name {
pub const MAX: usize = $array_len;
+
+ const ITEM_SCHEMA: ::proxmox_schema::Schema =
+ ::proxmox_api_macro::json_schema! $api_def ;
+
+ const ARRAY_OBJECT_SCHEMA: Schema = const {
+ const BUFSIZE: usize = (stringify!($field_prefix).len() + $crate::types::__DIGIT_SPACE) * $array_len;
+
+ const NAMES: [u8; BUFSIZE] = const {
+ let mut buffer = [0u8; BUFSIZE];
+ $crate::types::__fill_names::<$array_len>(stringify!($field_prefix), &mut buffer);
+ buffer
+ };
+
+ const PROPERTIES: [::proxmox_schema::SchemaPropertyEntry; $array_len] = const {
+ let mut properties = [("", false, &$type_name::ITEM_SCHEMA); $array_len];
+ $crate::types::__fill_properties::<$array_len>(stringify!($field_prefix), &NAMES, &mut properties);
+ properties
+ };
+
+ ::proxmox_schema::ObjectSchema::new(
+ concat!("Container for the `", stringify!($field_prefix), "[N]` fields."),
+ &PROPERTIES,
+ ).schema()
+ };
}
impl ::proxmox_schema::ApiType for $type_name {
- const API_SCHEMA: ::proxmox_schema::Schema =
- ::proxmox_api_macro::json_schema! $api_def ;
+ const API_SCHEMA: ::proxmox_schema::Schema = Self::ARRAY_OBJECT_SCHEMA;
}
impl std::ops::Deref for $type_name {
diff --git a/pve-api-types/src/types/mod.rs b/pve-api-types/src/types/mod.rs
index fe52a169..66171e39 100644
--- a/pve-api-types/src/types/mod.rs
+++ b/pve-api-types/src/types/mod.rs
@@ -11,7 +11,10 @@ use serde_json::Value;
use proxmox_schema::{api, const_regex, ApiStringFormat, ApiType, Schema, StringSchema};
mod macros;
+pub(crate) use macros::__fill_names;
+pub(crate) use macros::__fill_properties;
use macros::generate_array_field;
+pub(crate) use macros::__DIGIT_SPACE;
pub mod array;
pub mod stringlist;
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
next prev parent reply other threads:[~2025-11-13 11:59 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-13 12:00 [pdm-devel] [PATCH datacenter-manager/proxmox{, -backup}/widget-toolkit v2 0/9] unstable flag and pdm api viewer Shannon Sterz
2025-11-13 12:00 ` [pdm-devel] [PATCH proxmox v2 1/5] router/api-macro: add unstable flag for ApiMethod Shannon Sterz
2025-11-13 12:00 ` Shannon Sterz [this message]
2025-11-13 12:00 ` [pdm-devel] [PATCH proxmox v2 3/5] pve-api-types: fix clippy lints Shannon Sterz
2025-11-13 12:00 ` [pdm-devel] [PATCH proxmox v2 4/5] docgen: add docgen crate Shannon Sterz
2025-11-13 12:00 ` [pdm-devel] [PATCH proxmox v2 5/5] docgen: add support for the new stable flag Shannon Sterz
2025-11-13 12:00 ` [pdm-devel] [PATCH widget-toolkit v2 1/1] api viewer: add support for endpoints that are marked as unstable Shannon Sterz
2025-11-13 12:00 ` [pdm-devel] [PATCH proxmox-backup v2 1/1] docgen: use proxmox-rs docgen crate Shannon Sterz
2025-11-13 12:00 ` [pdm-devel] [PATCH datacenter-manager v2 1/2] docgen: switch to " Shannon Sterz
2025-11-13 12:00 ` [pdm-devel] [PATCH datacenter-manager v2 2/2] api-viewer: add an api-viewer package Shannon Sterz
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=20251113120021.331639-3-s.sterz@proxmox.com \
--to=s.sterz@proxmox.com \
--cc=pdm-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