public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
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


  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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal