From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 9F2D61FF17E for ; Thu, 13 Nov 2025 12:59:37 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4519C19F64; Thu, 13 Nov 2025 13:00:29 +0100 (CET) From: Shannon Sterz To: pdm-devel@lists.proxmox.com Date: Thu, 13 Nov 2025 13:00:14 +0100 Message-ID: <20251113120021.331639-3-s.sterz@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251113120021.331639-1-s.sterz@proxmox.com> References: <20251113120021.331639-1-s.sterz@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1763035198057 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.061 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pdm-devel] [PATCH proxmox v2 2/5] pve-api-types: generate array objects X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" From: Wolfgang Bumiller 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 [SS: minor clean up in commit message] Signed-off-by: Shannon Sterz --- 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 { + // 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(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( + 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