* [PATCH proxmox v3 1/2] add proxmox-const-utils crate, factor out checks from proxmox-schema
2026-09-14 12:30 [PATCH proxmox v3 0/2] check privilege paths during compilation Dominik Csapak
@ 2026-09-14 12:30 ` Dominik Csapak
2026-09-14 12:30 ` [PATCH proxmox v3 2/2] router: compile time check privilege path parameters for existence Dominik Csapak
1 sibling, 0 replies; 3+ messages in thread
From: Dominik Csapak @ 2026-09-14 12:30 UTC (permalink / raw)
To: pdm-devel
We want to do some const fn checks in multiple crates, and
proxmox-schema already contained the one we need, so factor it out.
For convenience also add a simple `byte_string_eq`. Also add some basic
tests.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
Cargo.toml | 2 +
proxmox-const-utils/Cargo.toml | 13 +++++
proxmox-const-utils/debian/changelog | 5 ++
proxmox-const-utils/debian/control | 30 +++++++++++
proxmox-const-utils/debian/copyright | 18 +++++++
proxmox-const-utils/debian/debcargo.toml | 7 +++
proxmox-const-utils/src/lib.rs | 66 ++++++++++++++++++++++++
proxmox-schema/Cargo.toml | 1 +
proxmox-schema/src/const_test_utils.rs | 25 ---------
proxmox-schema/src/lib.rs | 2 -
proxmox-schema/src/schema.rs | 10 ++--
11 files changed, 147 insertions(+), 32 deletions(-)
create mode 100644 proxmox-const-utils/Cargo.toml
create mode 100644 proxmox-const-utils/debian/changelog
create mode 100644 proxmox-const-utils/debian/control
create mode 100644 proxmox-const-utils/debian/copyright
create mode 100644 proxmox-const-utils/debian/debcargo.toml
create mode 100644 proxmox-const-utils/src/lib.rs
delete mode 100644 proxmox-schema/src/const_test_utils.rs
diff --git a/Cargo.toml b/Cargo.toml
index f631b404..ba29c8bb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ members = [
"proxmox-client",
"proxmox-compression",
"proxmox-config-digest",
+ "proxmox-const-utils",
"proxmox-daemon",
"proxmox-deb-version",
"proxmox-disks",
@@ -166,6 +167,7 @@ proxmox-auth-api = { version = "1.0.5", path = "proxmox-auth-api" }
proxmox-async = { version = "0.5.0", path = "proxmox-async" }
proxmox-base64 = { version = "1.0.0", path = "proxmox-base64" }
proxmox-compression = { version = "1.0.0", path = "proxmox-compression" }
+proxmox-const-utils = { version = "0.1.0", path = "proxmox-const-utils" }
proxmox-daemon = { version = "1.0.0", path = "proxmox-daemon" }
proxmox-disks = { version = "0.2.0", path = "proxmox-disks" }
proxmox-fixed-string = { version = "0.1.0", path = "proxmox-fixed-string" }
diff --git a/proxmox-const-utils/Cargo.toml b/proxmox-const-utils/Cargo.toml
new file mode 100644
index 00000000..914c5fd3
--- /dev/null
+++ b/proxmox-const-utils/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "proxmox-const-utils"
+description = "const fn utilities used by proxmox crates"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+homepage.workspace = true
+exclude.workspace = true
+rust-version.workspace = true
+
+[dependencies]
diff --git a/proxmox-const-utils/debian/changelog b/proxmox-const-utils/debian/changelog
new file mode 100644
index 00000000..fc49a60f
--- /dev/null
+++ b/proxmox-const-utils/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-const-utils (0.1.0) trixie; urgency=medium
+
+ * initial version
+
+ -- Proxmox Support Team <support@proxmox.com> Mon, 14 Sep 2026 13:38:43 +0200
diff --git a/proxmox-const-utils/debian/control b/proxmox-const-utils/debian/control
new file mode 100644
index 00000000..e45bf06a
--- /dev/null
+++ b/proxmox-const-utils/debian/control
@@ -0,0 +1,30 @@
+Source: rust-proxmox-const-utils
+Section: rust
+Priority: optional
+Build-Depends: debhelper-compat (= 13),
+ dh-sequence-cargo
+Build-Depends-Arch: cargo:native <!nocheck>,
+ rustc:native (>= 1.85) <!nocheck>,
+ libstd-rust-dev <!nocheck>
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Standards-Version: 4.7.2
+Vcs-Git: git://git.proxmox.com/git/proxmox.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
+Homepage: https://proxmox.com
+X-Cargo-Crate: proxmox-const-utils
+
+Package: librust-proxmox-const-utils-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends}
+Provides:
+ librust-proxmox-const-utils+default-dev (= ${binary:Version}),
+ librust-proxmox-const-utils-0-dev (= ${binary:Version}),
+ librust-proxmox-const-utils-0+default-dev (= ${binary:Version}),
+ librust-proxmox-const-utils-0.1-dev (= ${binary:Version}),
+ librust-proxmox-const-utils-0.1+default-dev (= ${binary:Version}),
+ librust-proxmox-const-utils-0.1.0-dev (= ${binary:Version}),
+ librust-proxmox-const-utils-0.1.0+default-dev (= ${binary:Version})
+Description: Const fn utilities used by proxmox crates - Rust source code
+ Source code for Debianized Rust crate "proxmox-const-utils"
diff --git a/proxmox-const-utils/debian/copyright b/proxmox-const-utils/debian/copyright
new file mode 100644
index 00000000..cb3e5adc
--- /dev/null
+++ b/proxmox-const-utils/debian/copyright
@@ -0,0 +1,18 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files:
+ *
+Copyright: 2025 - 2026 Proxmox Server Solutions GmbH <support@proxmox.com>
+License: AGPL-3.0-or-later
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option) any
+ later version.
+ .
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU Affero General Public License along
+ with this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/proxmox-const-utils/debian/debcargo.toml b/proxmox-const-utils/debian/debcargo.toml
new file mode 100644
index 00000000..b7864cdb
--- /dev/null
+++ b/proxmox-const-utils/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
diff --git a/proxmox-const-utils/src/lib.rs b/proxmox-const-utils/src/lib.rs
new file mode 100644
index 00000000..a4a4ceff
--- /dev/null
+++ b/proxmox-const-utils/src/lib.rs
@@ -0,0 +1,66 @@
+/// Note: this only compares *bytes* and is not strictly speaking equivalent to str::cmp!
+pub const fn byte_string_cmp(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
+ use std::cmp::Ordering::*;
+
+ // const-version of `min(a.len(), b.len())` while simultaneously remembering
+ // `cmp(a.len(), b.len())`.
+ let (end, len_result) = if a.len() < b.len() {
+ (a.len(), Less)
+ } else if a.len() > b.len() {
+ (b.len(), Greater)
+ } else {
+ (a.len(), Equal)
+ };
+
+ let mut i = 0;
+ while i != end {
+ if a[i] < b[i] {
+ return Less;
+ } else if a[i] > b[i] {
+ return Greater;
+ }
+ i += 1;
+ }
+ len_result
+}
+
+/// As with [`byte_string_cmp`] this only compares bytes.
+pub const fn byte_string_eq(a: &[u8], b: &[u8]) -> bool {
+ matches!(byte_string_cmp(a, b), std::cmp::Ordering::Equal)
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{byte_string_cmp, byte_string_eq};
+ use std::cmp::Ordering::{self, *};
+
+ fn str_cmp(a: &str, b: &str, res: Ordering) {
+ assert_eq!(byte_string_cmp(a.as_bytes(), b.as_bytes()), res);
+ }
+
+ fn str_eq(a: &str, b: &str, res: bool) {
+ assert_eq!(byte_string_eq(a.as_bytes(), b.as_bytes()), res);
+ }
+
+ #[test]
+ fn test_cmp() {
+ str_cmp("foo", "bar", Greater);
+ str_cmp("bar", "foo", Less);
+ str_cmp("foo", "fooo", Less);
+ str_cmp("fooo", "foo", Greater);
+ str_cmp("foo", "foo", Equal);
+ str_cmp("", "", Equal);
+ str_cmp("", "foo", Less);
+ str_cmp("foo", "", Greater);
+ }
+
+ #[test]
+ fn test_eq() {
+ str_eq("", "", true);
+ str_eq("foo", "foo", true);
+ str_eq("foo", "", false);
+ str_eq("", "foo", false);
+ str_eq("bar", "foo", false);
+ str_eq("foo", "bar", false);
+ }
+}
diff --git a/proxmox-schema/Cargo.toml b/proxmox-schema/Cargo.toml
index 79eab333..b2de56dd 100644
--- a/proxmox-schema/Cargo.toml
+++ b/proxmox-schema/Cargo.toml
@@ -24,6 +24,7 @@ libc = { workspace = true, optional = true }
nix = { workspace = true, optional = true, features = [ "feature" ] }
proxmox-api-macro = { workspace = true, optional = true }
+proxmox-const-utils.workspace = true
[dev-dependencies]
url.workspace = true
diff --git a/proxmox-schema/src/const_test_utils.rs b/proxmox-schema/src/const_test_utils.rs
deleted file mode 100644
index 3613ac35..00000000
--- a/proxmox-schema/src/const_test_utils.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-/// Note: this only compares *bytes* and is not strictly speaking equivalent to str::cmp!
-pub const fn byte_string_cmp(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
- use std::cmp::Ordering::*;
-
- // const-version of `min(a.len(), b.len())` while simultaneously remembering
- // `cmp(a.len(), b.len())`.
- let (end, len_result) = if a.len() < b.len() {
- (a.len(), Less)
- } else if a.len() > b.len() {
- (b.len(), Greater)
- } else {
- (a.len(), Equal)
- };
-
- let mut i = 0;
- while i != end {
- if a[i] < b[i] {
- return Less;
- } else if a[i] > b[i] {
- return Greater;
- }
- i += 1;
- }
- len_result
-}
diff --git a/proxmox-schema/src/lib.rs b/proxmox-schema/src/lib.rs
index 727822f0..5175280c 100644
--- a/proxmox-schema/src/lib.rs
+++ b/proxmox-schema/src/lib.rs
@@ -36,5 +36,3 @@ pub mod upid;
#[cfg(feature = "api-types")]
pub mod api_types;
-
-pub(crate) mod const_test_utils;
diff --git a/proxmox-schema/src/schema.rs b/proxmox-schema/src/schema.rs
index 378c6290..97e502a7 100644
--- a/proxmox-schema/src/schema.rs
+++ b/proxmox-schema/src/schema.rs
@@ -655,7 +655,7 @@ const fn assert_properties_sorted(properties: SchemaPropertyMap) {
while i != properties.len() {
let cur = properties[i].0;
if let Some(prev) = prev {
- match crate::const_test_utils::byte_string_cmp(prev.as_bytes(), cur.as_bytes()) {
+ match proxmox_const_utils::byte_string_cmp(prev.as_bytes(), cur.as_bytes()) {
Ordering::Greater => panic!("object schema properties must be sorted"),
Ordering::Equal => panic!("duplicate object schema properties not allowed"),
Ordering::Less => (),
@@ -672,7 +672,7 @@ const fn property_map_contains(properties: SchemaPropertyMap, needle: &'static s
let mut i = 0;
while i != properties.len() {
if let Ordering::Equal =
- crate::const_test_utils::byte_string_cmp(properties[i].0.as_bytes(), needle.as_bytes())
+ proxmox_const_utils::byte_string_cmp(properties[i].0.as_bytes(), needle.as_bytes())
{
return true;
}
@@ -688,7 +688,7 @@ const fn assert_key_not_alias(key: &str, aliases: PropertyAliasMap) {
while i != aliases.len() {
let (alias, _) = aliases[i];
if let Ordering::Equal =
- crate::const_test_utils::byte_string_cmp(alias.as_bytes(), key.as_bytes())
+ proxmox_const_utils::byte_string_cmp(alias.as_bytes(), key.as_bytes())
{
panic!("key must reference a canonical property, not an alias");
}
@@ -704,7 +704,7 @@ const fn assert_property_aliases_valid(aliases: PropertyAliasMap, properties: Sc
while i != aliases.len() {
let (alias, target) = aliases[i];
if let Some(prev) = prev {
- match crate::const_test_utils::byte_string_cmp(prev.as_bytes(), alias.as_bytes()) {
+ match proxmox_const_utils::byte_string_cmp(prev.as_bytes(), alias.as_bytes()) {
Ordering::Greater => panic!("property aliases must be sorted by alias name"),
Ordering::Equal => panic!("duplicate property alias not allowed"),
Ordering::Less => (),
@@ -1050,7 +1050,7 @@ const fn assert_one_of_list_is_sorted(list: &[(&str, &Schema)]) {
while i != list.len() {
let cur = list[i].0;
if let Some(prev) = prev {
- match crate::const_test_utils::byte_string_cmp(prev.as_bytes(), cur.as_bytes()) {
+ match proxmox_const_utils::byte_string_cmp(prev.as_bytes(), cur.as_bytes()) {
Ordering::Greater => panic!("oneOf variant list must be sorted"),
Ordering::Equal => panic!("multiple variants of the same type"),
Ordering::Less => (),
--
2.47.3
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH proxmox v3 2/2] router: compile time check privilege path parameters for existence
2026-09-14 12:30 [PATCH proxmox v3 0/2] check privilege paths during compilation Dominik Csapak
2026-09-14 12:30 ` [PATCH proxmox v3 1/2] add proxmox-const-utils crate, factor out checks from proxmox-schema Dominik Csapak
@ 2026-09-14 12:30 ` Dominik Csapak
1 sibling, 0 replies; 3+ messages in thread
From: Dominik Csapak @ 2026-09-14 12:30 UTC (permalink / raw)
To: pdm-devel
In an API privilege, path components can be interpolated from
parameters, e.g. 'foo/{bar}'. If this parameter does not exist,
the api privilege check fails and the client gets a 403 error back.
Instead of only checking this at runtime, check the existence of the
parameter in the schema during compilation since the schemas need to
be const anyway. In case of 'additional_properties', we can only
check it during runtime, but currently there are no such cases for the
API where a dynamic parameter would be used in the privilege path.
(And it does not make sense to not statically define such a parameter)
This also checks existence of parameters referenced with
`Permission::WithParam` and `Permission::UserParam`.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
proxmox-router/Cargo.toml | 1 +
proxmox-router/src/router.rs | 235 ++++++++++++++++++++++++++++++++++-
2 files changed, 235 insertions(+), 1 deletion(-)
diff --git a/proxmox-router/Cargo.toml b/proxmox-router/Cargo.toml
index 7725da4e..60a33d44 100644
--- a/proxmox-router/Cargo.toml
+++ b/proxmox-router/Cargo.toml
@@ -46,6 +46,7 @@ unicode-width ="0.2"
rustyline = { version = "14", optional = true }
libc = { workspace = true, optional = true }
+proxmox-const-utils.workspace = true
proxmox-http = { workspace = true, optional = true }
proxmox-http-error.workspace = true
proxmox-schema.workspace = true
diff --git a/proxmox-router/src/router.rs b/proxmox-router/src/router.rs
index fea47ba6..f82d24f6 100644
--- a/proxmox-router/src/router.rs
+++ b/proxmox-router/src/router.rs
@@ -11,12 +11,13 @@ use http::{Method, Response};
#[cfg(feature = "server")]
use hyper::body::Incoming;
use percent_encoding::percent_decode_str;
+use proxmox_const_utils::byte_string_eq;
#[cfg(feature = "server")]
use proxmox_http::Body;
use serde::Serialize;
use serde_json::Value;
-use proxmox_schema::{ObjectSchema, ParameterSchema, ReturnType, Schema};
+use proxmox_schema::{AllOfSchema, ObjectSchema, OneOfSchema, ParameterSchema, ReturnType, Schema};
use super::Permission;
use crate::RpcEnvironment;
@@ -831,6 +832,128 @@ impl std::fmt::Debug for ApiMethod {
}
}
+// const helpers to check privilege parameters
+
+const fn object_schema_has_parameter(object: &ObjectSchema, name: &[u8]) -> bool {
+ // additional properties are not statically known, so any name could exist
+ if object.additional_properties {
+ return true;
+ }
+ let mut i = 0;
+ while i < object.properties.len() {
+ if byte_string_eq(object.properties[i].0.as_bytes(), name) {
+ return true;
+ }
+ i += 1;
+ }
+ false
+}
+
+const fn all_of_schema_has_parameter(all_of: &AllOfSchema, name: &[u8]) -> bool {
+ let mut i = 0;
+ while i < all_of.list.len() {
+ if schema_has_parameter(all_of.list[i], name) {
+ return true;
+ }
+ i += 1;
+ }
+ false
+}
+
+const fn one_of_schema_has_parameter(one_of: &OneOfSchema, name: &[u8]) -> bool {
+ if byte_string_eq(one_of.type_property_entry.0.as_bytes(), name) {
+ return true;
+ }
+ let mut i = 0;
+ while i < one_of.list.len() {
+ if schema_has_parameter(one_of.list[i].1, name) {
+ return true;
+ }
+ i += 1;
+ }
+ false
+}
+
+const fn schema_has_parameter(schema: &Schema, name: &[u8]) -> bool {
+ match schema {
+ Schema::Object(object) => object_schema_has_parameter(object, name),
+ Schema::AllOf(all_of) => all_of_schema_has_parameter(all_of, name),
+ Schema::OneOf(one_of) => one_of_schema_has_parameter(one_of, name),
+ _ => false,
+ }
+}
+
+const fn parameter_exists(parameters: ParameterSchema, name: &[u8]) -> bool {
+ match parameters {
+ ParameterSchema::Object(object) => object_schema_has_parameter(object, name),
+ ParameterSchema::AllOf(all_of) => all_of_schema_has_parameter(all_of, name),
+ ParameterSchema::OneOf(one_of) => one_of_schema_has_parameter(one_of, name),
+ }
+}
+
+// mirrors the splitting done by check_api_permission: a component can contain multiple '/'
+// separated parts, each of which may be a '{name}' parameter reference
+const fn check_privilege_path_components(component: &str, parameters: ParameterSchema) {
+ let bytes = component.as_bytes();
+ // early return for strings that can't interpolate
+ if bytes.len() < 2 {
+ return;
+ }
+ let mut component_start = 0;
+ let mut pos = 0;
+ while pos <= bytes.len() {
+ if pos == bytes.len() || bytes[pos] == b'/' {
+ let component_len = pos - component_start;
+ if component_len >= 2 && bytes[component_start] == b'{' && bytes[pos - 1] == b'}' {
+ if component_len == 2 {
+ panic!("empty parameter declaration");
+ }
+ // double split_at because range slicing is not const
+ let name = bytes.split_at(pos - 1).0.split_at(component_start + 1).1;
+ if !parameter_exists(parameters, name) {
+ panic!(
+ "privilege path references a parameter that does not exist in the method's \
+ parameter schema"
+ );
+ }
+ }
+ component_start = pos + 1;
+ }
+ pos += 1;
+ }
+}
+
+const fn assert_path_parameters_exist(perm: &Permission, parameters: ParameterSchema) {
+ match perm {
+ Permission::WithParam(name, permission) => {
+ if !parameter_exists(parameters, name.as_bytes()) {
+ panic!("given user parameter does not exist");
+ }
+ assert_path_parameters_exist(permission, parameters)
+ }
+ Permission::UserParam(name) => {
+ if !parameter_exists(parameters, name.as_bytes()) {
+ panic!("given user parameter does not exist");
+ }
+ }
+ Permission::Privilege(paths, _, _) => {
+ let mut i = 0;
+ while i < paths.len() {
+ check_privilege_path_components(paths[i], parameters);
+ i += 1;
+ }
+ }
+ Permission::And(permissions) | Permission::Or(permissions) => {
+ let mut i = 0;
+ while i < permissions.len() {
+ assert_path_parameters_exist(permissions[i], parameters);
+ i += 1;
+ }
+ }
+ _ => (),
+ }
+}
+
impl ApiMethod {
pub const fn new_full(handler: &'static ApiHandler, parameters: ParameterSchema) -> Self {
Self {
@@ -890,11 +1013,19 @@ impl ApiMethod {
self
}
+ /// Set the access permissions.
+ ///
+ /// This asserts that every '{name}' parameter reference in `Privilege` permission paths, and
+ /// the parameters in `UserParam` and `WithParam` exists in the method's parameter schema,
+ /// since such a parameter could otherwise never match at runtime. Since API methods are usually
+ /// built in a const context, a violation is a compile time error.
pub const fn access(
mut self,
description: Option<&'static str>,
permission: &'static Permission,
) -> Self {
+ assert_path_parameters_exist(permission, self.parameters);
+
self.access = ApiAccess {
description,
permission,
@@ -903,3 +1034,105 @@ impl ApiMethod {
self
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use proxmox_schema::StringSchema;
+
+ const STRING_SCHEMA: Schema = StringSchema::new("test").schema();
+
+ const PARAMETERS: ObjectSchema = ObjectSchema::new(
+ "test parameters",
+ &[
+ ("bar", true, &STRING_SCHEMA),
+ ("foo", false, &STRING_SCHEMA),
+ ],
+ );
+
+ const ADDITIONAL_PARAMETERS: ObjectSchema =
+ ObjectSchema::new("test parameters", &[]).additional_properties(true);
+
+ // compile time check that valid parameter references are accepted
+ const _: ApiMethod = ApiMethod::new_dummy(&PARAMETERS).access(
+ None,
+ &Permission::And(&[
+ &Permission::Privilege(&["foo", "{foo}", "{bar}"], 1, true),
+ &Permission::Privilege(&["foo", "{foo}/{bar}"], 1, true),
+ &Permission::WithParam(
+ "foo",
+ &Permission::Or(&[&Permission::Privilege(&["foo", "{foo}"], 1, true)]),
+ ),
+ &Permission::Privilege(&["/"], 1, true),
+ &Permission::Privilege(&["/foo"], 1, true),
+ ]),
+ );
+
+ #[test]
+ #[should_panic(expected = "privilege path references a parameter")]
+ fn missing_privilege_path_parameter() {
+ let _ = ApiMethod::new_dummy(&PARAMETERS)
+ .access(None, &Permission::Privilege(&["foo", "{baz}"], 1, true));
+ }
+
+ #[test]
+ #[should_panic(expected = "given user parameter does not exist")]
+ fn missing_privilege_user_parameter() {
+ let _ = ApiMethod::new_dummy(&PARAMETERS).access(
+ None,
+ &Permission::WithParam(
+ "not-existing",
+ &Permission::Privilege(&["foo", "{baz}"], 1, true),
+ ),
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "given user parameter does not exist")]
+ fn missing_user_parameter() {
+ let _ =
+ ApiMethod::new_dummy(&PARAMETERS).access(None, &Permission::UserParam("not-existing"));
+ }
+
+ #[test]
+ #[should_panic(expected = "empty parameter declaration")]
+ fn missing_privilege_path_parameter_name() {
+ let _ = ApiMethod::new_dummy(&PARAMETERS)
+ .access(None, &Permission::Privilege(&["foo", "{}"], 1, true));
+ }
+
+ #[test]
+ #[should_panic(expected = "privilege path references a parameter")]
+ fn missing_parameter_in_combined_component() {
+ let _ = ApiMethod::new_dummy(&PARAMETERS).access(
+ None,
+ &Permission::Or(&[&Permission::Privilege(
+ &["datastore", "{foo}/{baz}"],
+ 0b01,
+ true,
+ )]),
+ );
+ }
+
+ #[test]
+ fn malformed_parameter_in_combined_component() {
+ // should work, components are not enclosed in brackets properly so no interpolation should
+ // be done
+ let _ = ApiMethod::new_dummy(&PARAMETERS).access(
+ None,
+ &Permission::Or(&[&Permission::Privilege(&["foo", "bar/{baz/}"], 1, true)]),
+ );
+
+ let _ = ApiMethod::new_dummy(&PARAMETERS).access(
+ None,
+ &Permission::Or(&[&Permission::Privilege(&["foo", "{bar/baz}/"], 1, true)]),
+ );
+ }
+
+ #[test]
+ fn additional_properties_allow_any_parameter() {
+ let _ = ApiMethod::new_dummy(&ADDITIONAL_PARAMETERS)
+ .access(None, &Permission::Privilege(&["foo", "{baz}"], 1, true));
+ }
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 3+ messages in thread