add proxmox-schema and proxmox-router crates

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2021-10-07 09:36:06 +02:00
parent 01a8b6f1bf
commit 41f3fdfeb9
62 changed files with 1625 additions and 1073 deletions

View File

@ -6,6 +6,8 @@ members = [
"proxmox-http",
"proxmox-io",
"proxmox-lang",
"proxmox-router",
"proxmox-schema",
"proxmox-sortable-macro",
"proxmox-tfa",
"proxmox-time",

View File

@ -7,6 +7,8 @@ CRATES = \
proxmox-http \
proxmox-io \
proxmox-lang \
proxmox-router \
proxmox-schema \
proxmox-sortable-macro \
proxmox-tfa \
proxmox-time \

View File

@ -1,7 +1,7 @@
[package]
name = "proxmox-api-macro"
edition = "2018"
version = "0.5.1"
version = "1.0.0"
authors = [ "Wolfgang Bumiller <w.bumiller@proxmox.com>" ]
license = "AGPL-3"
description = "Proxmox API macro"
@ -19,11 +19,19 @@ syn = { version = "1.0", features = [ "extra-traits", "full", "visit-mut" ] }
[dev-dependencies]
futures = "0.3"
proxmox = { version = "0.13.0", path = "../proxmox", features = [ "test-harness", "api-macro" ] }
serde = "1.0"
serde_derive = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
[dev-dependencies.proxmox-schema]
version = "1.0.0"
path = "../proxmox-schema"
features = [ "test-harness", "api-macro" ]
[dev-dependencies.proxmox-router]
version = "1.0.0"
path = "../proxmox-router"
features = [ "test-harness" ]
# [features]
# # Used to quickly filter out the serde derive noise when using `cargo expand` for debugging!
# # Add this in case you need it, but don't commit it (to avoid debcargo picking this up)!

View File

@ -1,3 +1,9 @@
rust-proxmox-api-macro (1.0.0-1) stable; urgency=medium
* schema was split out of proxmox into a new proxmox-schema crate
-- Proxmox Support Team <support@proxmox.com> Thu, 07 Oct 2021 14:28:14 +0200
rust-proxmox-api-macro (0.5.1-1) stable; urgency=medium
* allow external `returns` specification on methods, refereincing a

View File

@ -33,12 +33,12 @@ Depends:
librust-syn-1+visit-mut-dev
Provides:
librust-proxmox-api-macro+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-0-dev (= ${binary:Version}),
librust-proxmox-api-macro-0+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-0.5-dev (= ${binary:Version}),
librust-proxmox-api-macro-0.5+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-0.5.1-dev (= ${binary:Version}),
librust-proxmox-api-macro-0.5.1+default-dev (= ${binary:Version})
librust-proxmox-api-macro-1-dev (= ${binary:Version}),
librust-proxmox-api-macro-1+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0.0-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0.0+default-dev (= ${binary:Version})
Description: Proxmox API macro - Rust source code
This package contains the source for the Rust proxmox-api-macro crate, packaged
by debcargo for use with cargo and dh-cargo.

View File

@ -65,7 +65,7 @@ pub fn handle_enum(
};
variants.extend(quote_spanned! { variant.ident.span() =>
::proxmox::api::schema::EnumEntry {
::proxmox_schema::EnumEntry {
value: #variant_string,
description: #comment,
},
@ -78,14 +78,14 @@ pub fn handle_enum(
#enum_ty
#[automatically_derived]
impl ::proxmox::api::schema::ApiType for #name {
const API_SCHEMA: ::proxmox::api::schema::Schema =
impl ::proxmox_schema::ApiType for #name {
const API_SCHEMA: ::proxmox_schema::Schema =
#schema
.format(&::proxmox::api::schema::ApiStringFormat::Enum(&[#variants]))
.format(&::proxmox_schema::ApiStringFormat::Enum(&[#variants]))
.schema();
}
impl ::proxmox::api::schema::UpdaterType for #name {
impl ::proxmox_schema::UpdaterType for #name {
type Updater = Option<Self>;
}
})

View File

@ -77,7 +77,7 @@ impl ReturnSchema {
self.schema.to_schema(&mut out)?;
ts.extend(quote! {
::proxmox::api::router::ReturnType::new( #optional , &#out )
::proxmox_schema::ReturnType::new( #optional , &#out )
});
Ok(())
}
@ -218,16 +218,16 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
}
let api_handler = if is_async {
quote! { ::proxmox::api::ApiHandler::Async(&#api_func_name) }
quote! { ::proxmox_router::ApiHandler::Async(&#api_func_name) }
} else {
quote! { ::proxmox::api::ApiHandler::Sync(&#api_func_name) }
quote! { ::proxmox_router::ApiHandler::Sync(&#api_func_name) }
};
Ok(quote_spanned! { func.sig.span() =>
#input_schema_code
#vis const #api_method_name: ::proxmox::api::ApiMethod =
::proxmox::api::ApiMethod::new_full(
#vis const #api_method_name: ::proxmox_router::ApiMethod =
::proxmox_router::ApiMethod::new_full(
&#api_handler,
#input_schema_parameter,
)
@ -525,13 +525,13 @@ fn create_wrapper_function(
wrapper_ts.extend(quote! {
fn #api_func_name<'a>(
mut input_params: ::serde_json::Value,
api_method_param: &'static ::proxmox::api::ApiMethod,
rpc_env_param: &'a mut dyn ::proxmox::api::RpcEnvironment,
) -> ::proxmox::api::ApiFuture<'a> {
api_method_param: &'static ::proxmox_router::ApiMethod,
rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
) -> ::proxmox_router::ApiFuture<'a> {
//async fn func<'a>(
// mut input_params: ::serde_json::Value,
// api_method_param: &'static ::proxmox::api::ApiMethod,
// rpc_env_param: &'a mut dyn ::proxmox::api::RpcEnvironment,
// api_method_param: &'static ::proxmox_router::ApiMethod,
// rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
//) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
// #body
//}
@ -545,8 +545,8 @@ fn create_wrapper_function(
wrapper_ts.extend(quote! {
fn #api_func_name(
mut input_params: ::serde_json::Value,
api_method_param: &::proxmox::api::ApiMethod,
rpc_env_param: &mut dyn ::proxmox::api::RpcEnvironment,
api_method_param: &::proxmox_router::ApiMethod,
rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
#body
}
@ -650,7 +650,7 @@ fn extract_normal_parameter(
let ty = param.ty;
body.extend(quote_spanned! { span =>
let #arg_name = <#ty as ::serde::Deserialize>::deserialize(
::proxmox::api::de::ExtractValueDeserializer::try_new(
::proxmox_schema::de::ExtractValueDeserializer::try_new(
input_map,
#schema_ref,
)
@ -703,10 +703,10 @@ fn serialize_input_schema(
input_schema.to_typed_schema(&mut ts)?;
return Ok((
quote_spanned! { func_sig_span =>
pub const #input_schema_name: ::proxmox::api::schema::ObjectSchema = #ts;
pub const #input_schema_name: ::proxmox_schema::ObjectSchema = #ts;
},
quote_spanned! { func_sig_span =>
::proxmox::api::schema::ParameterSchema::Object(&#input_schema_name)
::proxmox_schema::ParameterSchema::Object(&#input_schema_name)
},
));
}
@ -758,7 +758,7 @@ fn serialize_input_schema(
(
quote_spanned!(func_sig_span =>
const #inner_schema_name: ::proxmox::api::schema::Schema = #obj_schema;
const #inner_schema_name: ::proxmox_schema::Schema = #obj_schema;
),
quote_spanned!(func_sig_span => &#inner_schema_name,),
)
@ -771,8 +771,8 @@ fn serialize_input_schema(
quote_spanned! { func_sig_span =>
#inner_schema
pub const #input_schema_name: ::proxmox::api::schema::AllOfSchema =
::proxmox::api::schema::AllOfSchema::new(
pub const #input_schema_name: ::proxmox_schema::AllOfSchema =
::proxmox_schema::AllOfSchema::new(
#description,
&[
#inner_schema_ref
@ -781,7 +781,7 @@ fn serialize_input_schema(
);
},
quote_spanned! { func_sig_span =>
::proxmox::api::schema::ParameterSchema::AllOf(&#input_schema_name)
::proxmox_schema::ParameterSchema::AllOf(&#input_schema_name)
},
))
}

View File

@ -148,7 +148,7 @@ impl Schema {
fn to_schema_reference(&self) -> Option<TokenStream> {
match &self.item {
SchemaItem::ExternType(path) => Some(
quote_spanned! { path.span() => &<#path as ::proxmox::api::schema::ApiType>::API_SCHEMA },
quote_spanned! { path.span() => &<#path as ::proxmox_schema::ApiType>::API_SCHEMA },
),
SchemaItem::ExternSchema(path) => Some(quote_spanned! { path.span() => &#path }),
_ => None,
@ -323,31 +323,31 @@ impl SchemaItem {
SchemaItem::Null(span) => {
let description = check_description()?;
ts.extend(quote_spanned! { *span =>
::proxmox::api::schema::NullSchema::new(#description)
::proxmox_schema::NullSchema::new(#description)
});
}
SchemaItem::Boolean(span) => {
let description = check_description()?;
ts.extend(quote_spanned! { *span =>
::proxmox::api::schema::BooleanSchema::new(#description)
::proxmox_schema::BooleanSchema::new(#description)
});
}
SchemaItem::Integer(span) => {
let description = check_description()?;
ts.extend(quote_spanned! { *span =>
::proxmox::api::schema::IntegerSchema::new(#description)
::proxmox_schema::IntegerSchema::new(#description)
});
}
SchemaItem::Number(span) => {
let description = check_description()?;
ts.extend(quote_spanned! { *span =>
::proxmox::api::schema::NumberSchema::new(#description)
::proxmox_schema::NumberSchema::new(#description)
});
}
SchemaItem::String(span) => {
let description = check_description()?;
ts.extend(quote_spanned! { *span =>
::proxmox::api::schema::StringSchema::new(#description)
::proxmox_schema::StringSchema::new(#description)
});
}
SchemaItem::Object(obj) => {
@ -355,7 +355,7 @@ impl SchemaItem {
let mut elems = TokenStream::new();
obj.to_schema_inner(&mut elems)?;
ts.extend(quote_spanned! { obj.span =>
::proxmox::api::schema::ObjectSchema::new(#description, &[#elems])
::proxmox_schema::ObjectSchema::new(#description, &[#elems])
});
}
SchemaItem::Array(array) => {
@ -363,7 +363,7 @@ impl SchemaItem {
let mut items = TokenStream::new();
array.to_schema(&mut items)?;
ts.extend(quote_spanned! { array.span =>
::proxmox::api::schema::ArraySchema::new(#description, &#items)
::proxmox_schema::ArraySchema::new(#description, &#items)
});
}
SchemaItem::ExternType(path) => {
@ -375,7 +375,7 @@ impl SchemaItem {
error!(description => "description not allowed on external type");
}
ts.extend(quote_spanned! { path.span() => <#path as ::proxmox::api::schema::ApiType>::API_SCHEMA });
ts.extend(quote_spanned! { path.span() => <#path as ::proxmox_schema::ApiType>::API_SCHEMA });
return Ok(true);
}
SchemaItem::ExternSchema(path) => {

View File

@ -62,7 +62,7 @@ fn handle_unit_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<Toke
let name = &stru.ident;
let mut schema = finish_schema(schema, &stru, name)?;
schema.extend(quote_spanned! { name.span() =>
impl ::proxmox::api::schema::UpdaterType for #name {
impl ::proxmox_schema::UpdaterType for #name {
type Updater = Option<Self>;
}
});
@ -85,8 +85,8 @@ fn finish_schema(
#stru
#[automatically_derived]
impl ::proxmox::api::schema::ApiType for #name {
const API_SCHEMA: ::proxmox::api::schema::Schema = #schema;
impl ::proxmox_schema::ApiType for #name {
const API_SCHEMA: ::proxmox_schema::Schema = #schema;
}
})
}
@ -337,7 +337,7 @@ fn finish_all_of_struct(
(
quote_spanned!(name.span() =>
const INNER_API_SCHEMA: ::proxmox::api::schema::Schema = #obj_schema;
const INNER_API_SCHEMA: ::proxmox_schema::Schema = #obj_schema;
),
quote_spanned!(name.span() => &Self::INNER_API_SCHEMA,),
)
@ -354,9 +354,9 @@ fn finish_all_of_struct(
}
#[automatically_derived]
impl ::proxmox::api::schema::ApiType for #name {
const API_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::AllOfSchema::new(
impl ::proxmox_schema::ApiType for #name {
const API_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::AllOfSchema::new(
#description,
&[
#inner_schema_ref
@ -444,7 +444,7 @@ fn derive_updater(
if !is_empty_impl.is_empty() {
output.extend(quote::quote!(
#[automatically_derived]
impl ::proxmox::api::schema::Updater for #updater_name {
impl ::proxmox_schema::Updater for #updater_name {
fn is_empty(&self) -> bool {
#is_empty_impl
}
@ -453,7 +453,7 @@ fn derive_updater(
}
output.extend(quote::quote!(
impl ::proxmox::api::schema::UpdaterType for #original_name {
impl ::proxmox_schema::UpdaterType for #original_name {
type Updater = #updater_name;
}
));
@ -505,15 +505,11 @@ fn handle_updater_field(
qself: Some(syn::QSelf {
lt_token: syn::token::Lt { spans: [span] },
ty: Box::new(field.ty.clone()),
position: 4, // 'Updater' is the 4th item in the 'segments' below
position: 2, // 'Updater' is item index 2 in the 'segments' below
as_token: Some(syn::token::As { span }),
gt_token: syn::token::Gt { spans: [span] },
}),
path: util::make_path(
span,
true,
&["proxmox", "api", "schema", "UpdaterType", "Updater"],
),
path: util::make_path(span, true, &["proxmox_schema", "UpdaterType", "Updater"]),
};
// we also need to update the schema to point to the updater's schema for `type: Foo` entries
@ -530,7 +526,7 @@ fn handle_updater_field(
if field_schema.flatten_in_struct {
let updater_ty = &field.ty;
all_of_schemas
.extend(quote::quote! {&<#updater_ty as ::proxmox::api::schema::ApiType>::API_SCHEMA,});
.extend(quote::quote! {&<#updater_ty as ::proxmox_schema::ApiType>::API_SCHEMA,});
}
if !is_empty_impl.is_empty() {

View File

@ -69,7 +69,7 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
```
# use proxmox_api_macro::api;
# use proxmox::api::{ApiMethod, RpcEnvironment};
# use proxmox_router::{ApiMethod, RpcEnvironment};
use anyhow::Error;
use serde_json::Value;
@ -178,19 +178,19 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
```no_run
# struct RenamedStruct;
impl RenamedStruct {
pub const API_SCHEMA: &'static ::proxmox::api::schema::Schema =
&::proxmox::api::schema::ObjectSchema::new(
pub const API_SCHEMA: &'static ::proxmox_schema::Schema =
&::proxmox_schema::ObjectSchema::new(
"An example of a struct with renamed fields.",
&[
(
"test-string",
false,
&::proxmox::api::schema::StringSchema::new("A test string.").schema(),
&::proxmox_schema::StringSchema::new("A test string.").schema(),
),
(
"SomeOther",
true,
&::proxmox::api::schema::StringSchema::new(
&::proxmox_schema::StringSchema::new(
"An optional auto-derived value for testing:",
)
.schema(),

View File

@ -28,7 +28,7 @@ fn derive_updater_type(full_span: Span, ident: Ident, generics: syn::Generics) -
no_generics(generics);
quote_spanned! { full_span =>
impl ::proxmox::api::schema::UpdaterType for #ident {
impl ::proxmox_schema::UpdaterType for #ident {
type Updater = Option<Self>;
}
}

View File

@ -4,8 +4,9 @@ use anyhow::Error;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use proxmox::api::schema::{self, ApiType};
use proxmox_api_macro::api;
use proxmox_schema as schema;
use proxmox_schema::ApiType;
pub const NAME_SCHEMA: schema::Schema = schema::StringSchema::new("Name.").schema();
pub const VALUE_SCHEMA: schema::Schema = schema::IntegerSchema::new("Value.").schema();
@ -56,17 +57,16 @@ pub struct Nvit {
#[test]
fn test_nvit() {
const TEST_NAME_VALUE_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::ObjectSchema::new(
"Name and value.",
&[
("name", false, &NAME_SCHEMA),
("value", false, &VALUE_SCHEMA),
],
)
.schema();
const TEST_NAME_VALUE_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"Name and value.",
&[
("name", false, &NAME_SCHEMA),
("value", false, &VALUE_SCHEMA),
],
)
.schema();
const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::AllOfSchema::new(
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::AllOfSchema::new(
"Name, value, index and text.",
&[&TEST_NAME_VALUE_SCHEMA, &IndexText::API_SCHEMA],
)
@ -96,17 +96,17 @@ struct WithExtra {
#[test]
fn test_extra() {
const INNER_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::ObjectSchema::new(
const INNER_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"<INNER: Extra Schema>",
&[(
"extra",
false,
&::proxmox::api::schema::StringSchema::new("Extra field.").schema(),
&::proxmox_schema::StringSchema::new("Extra field.").schema(),
)],
)
.schema();
const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::AllOfSchema::new(
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::AllOfSchema::new(
"Extra Schema",
&[
&INNER_SCHEMA,
@ -134,9 +134,9 @@ pub fn hello(it: IndexText, nv: NameValue) -> Result<(NameValue, IndexText), Err
#[test]
fn hello_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new_full(
&::proxmox::api::ApiHandler::Sync(&api_function_hello),
::proxmox::api::schema::ParameterSchema::AllOf(&::proxmox::api::schema::AllOfSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new_full(
&::proxmox_router::ApiHandler::Sync(&api_function_hello),
::proxmox_schema::ParameterSchema::AllOf(&::proxmox_schema::AllOfSchema::new(
"Hello method.",
&[&IndexText::API_SCHEMA, &NameValue::API_SCHEMA],
)),
@ -164,19 +164,19 @@ pub fn with_extra(
#[test]
fn with_extra_schema_check() {
const INNER_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::ObjectSchema::new(
const INNER_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"<INNER: Extra method.>",
&[(
"extra",
false,
&::proxmox::api::schema::StringSchema::new("An extra field.").schema(),
&::proxmox_schema::StringSchema::new("An extra field.").schema(),
)],
)
.schema();
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new_full(
&::proxmox::api::ApiHandler::Sync(&api_function_with_extra),
::proxmox::api::schema::ParameterSchema::AllOf(&::proxmox::api::schema::AllOfSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new_full(
&::proxmox_router::ApiHandler::Sync(&api_function_with_extra),
::proxmox_schema::ParameterSchema::AllOf(&::proxmox_schema::AllOfSchema::new(
"Extra method.",
&[
&INNER_SCHEMA,
@ -189,7 +189,7 @@ fn with_extra_schema_check() {
}
struct RpcEnv;
impl proxmox::api::RpcEnvironment for RpcEnv {
impl proxmox_router::RpcEnvironment for RpcEnv {
fn result_attrib_mut(&mut self) -> &mut Value {
panic!("result_attrib_mut called");
}
@ -199,7 +199,7 @@ impl proxmox::api::RpcEnvironment for RpcEnv {
}
/// The environment type
fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
fn env_type(&self) -> proxmox_router::RpcEnvironmentType {
panic!("env_type called");
}

View File

@ -3,7 +3,7 @@ use proxmox_api_macro::api;
use anyhow::Error;
use serde_json::{json, Value};
use proxmox::api::Permission;
use proxmox_router::Permission;
#[api(
input: {
@ -59,15 +59,15 @@ pub fn create_ticket(param: Value) -> Result<Value, Error> {
#[test]
fn create_ticket_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Sync(&api_function_create_ticket),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_create_ticket),
&::proxmox_schema::ObjectSchema::new(
"Create or verify authentication ticket.",
&[
(
"password",
false,
&::proxmox::api::schema::StringSchema::new(
&::proxmox_schema::StringSchema::new(
"The secret password or a valid ticket.",
)
.schema(),
@ -75,22 +75,22 @@ fn create_ticket_schema_check() {
(
"username",
false,
&::proxmox::api::schema::StringSchema::new("User name")
&::proxmox_schema::StringSchema::new("User name")
.max_length(64)
.schema(),
),
],
),
)
.returns(::proxmox::api::router::ReturnType::new(
.returns(::proxmox_schema::ReturnType::new(
false,
&::proxmox::api::schema::ObjectSchema::new(
&::proxmox_schema::ObjectSchema::new(
"A ticket.",
&[
(
"CSRFPreventionToken",
false,
&::proxmox::api::schema::StringSchema::new(
&::proxmox_schema::StringSchema::new(
"Cross Site Request Forgerty Prevention Token.",
)
.schema(),
@ -98,12 +98,12 @@ fn create_ticket_schema_check() {
(
"ticket",
false,
&::proxmox::api::schema::StringSchema::new("Auth ticket.").schema(),
&::proxmox_schema::StringSchema::new("Auth ticket.").schema(),
),
(
"username",
false,
&::proxmox::api::schema::StringSchema::new("User name.").schema(),
&::proxmox_schema::StringSchema::new("User name.").schema(),
),
],
)
@ -162,15 +162,15 @@ pub fn create_ticket_direct(username: String, password: String) -> Result<&'stat
#[test]
fn create_ticket_direct_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Sync(&api_function_create_ticket_direct),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_create_ticket_direct),
&::proxmox_schema::ObjectSchema::new(
"Create or verify authentication ticket.",
&[
(
"password",
false,
&::proxmox::api::schema::StringSchema::new(
&::proxmox_schema::StringSchema::new(
"The secret password or a valid ticket.",
)
.schema(),
@ -178,22 +178,22 @@ fn create_ticket_direct_schema_check() {
(
"username",
false,
&::proxmox::api::schema::StringSchema::new("User name")
&::proxmox_schema::StringSchema::new("User name")
.max_length(64)
.schema(),
),
],
),
)
.returns(::proxmox::api::router::ReturnType::new(
.returns(::proxmox_schema::ReturnType::new(
false,
&::proxmox::api::schema::ObjectSchema::new(
&::proxmox_schema::ObjectSchema::new(
"A ticket.",
&[
(
"CSRFPreventionToken",
false,
&::proxmox::api::schema::StringSchema::new(
&::proxmox_schema::StringSchema::new(
"Cross Site Request Forgerty Prevention Token.",
)
.schema(),
@ -201,12 +201,12 @@ fn create_ticket_direct_schema_check() {
(
"ticket",
false,
&::proxmox::api::schema::StringSchema::new("Auth ticket.").schema(),
&::proxmox_schema::StringSchema::new("Auth ticket.").schema(),
),
(
"username",
false,
&::proxmox::api::schema::StringSchema::new("User name.").schema(),
&::proxmox_schema::StringSchema::new("User name.").schema(),
),
],
)
@ -258,14 +258,14 @@ pub fn func_with_option(verbose: Option<bool>) -> Result<(), Error> {
#[test]
fn func_with_option_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Sync(&api_function_func_with_option),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_func_with_option),
&::proxmox_schema::ObjectSchema::new(
"Optional parameter",
&[(
"verbose",
true,
&::proxmox::api::schema::BooleanSchema::new("Verbose output.").schema(),
&::proxmox_schema::BooleanSchema::new("Verbose output.").schema(),
)],
),
)
@ -275,7 +275,7 @@ fn func_with_option_schema_check() {
}
struct RpcEnv;
impl proxmox::api::RpcEnvironment for RpcEnv {
impl proxmox_router::RpcEnvironment for RpcEnv {
fn result_attrib_mut(&mut self) -> &mut Value {
panic!("result_attrib_mut called");
}
@ -285,7 +285,7 @@ impl proxmox::api::RpcEnvironment for RpcEnv {
}
/// The environment type
fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
fn env_type(&self) -> proxmox_router::RpcEnvironmentType {
panic!("env_type called");
}

View File

@ -34,14 +34,14 @@ pub async fn number(num: u32) -> Result<u32, Error> {
#[test]
fn number_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Async(&api_function_number),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Async(&api_function_number),
&::proxmox_schema::ObjectSchema::new(
"Return the number...",
&[(
"num",
false,
&::proxmox::api::schema::IntegerSchema::new("The version to upgrade to")
&::proxmox_schema::IntegerSchema::new("The version to upgrade to")
.minimum(0)
.maximum(0xffffffff)
.schema(),
@ -75,20 +75,20 @@ pub async fn more_async_params(param: Value) -> Result<(), Error> {
#[test]
fn more_async_params_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Async(&api_function_more_async_params),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Async(&api_function_more_async_params),
&::proxmox_schema::ObjectSchema::new(
"Return the number...",
&[
(
"bar",
false,
&::proxmox::api::schema::StringSchema::new("The great Bar").schema(),
&::proxmox_schema::StringSchema::new("The great Bar").schema(),
),
(
"foo",
false,
&::proxmox::api::schema::StringSchema::new("The great Foo").schema(),
&::proxmox_schema::StringSchema::new("The great Foo").schema(),
),
],
),
@ -116,14 +116,14 @@ pub async fn keyword_named_parameters(r#type: String) -> Result<(), Error> {
#[test]
fn keyword_named_parameters_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Async(&api_function_keyword_named_parameters),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Async(&api_function_keyword_named_parameters),
&::proxmox_schema::ObjectSchema::new(
"Returns nothing.",
&[(
"type",
false,
&::proxmox::api::schema::StringSchema::new("The great Foo").schema(),
&::proxmox_schema::StringSchema::new("The great Foo").schema(),
)],
),
)

View File

@ -1,8 +1,9 @@
//! This should test the usage of "external" schemas. If a property is declared with a path instead
//! of an object, we expect the path to lead to a schema.
use proxmox::api::{schema, RpcEnvironment};
use proxmox_api_macro::api;
use proxmox_router::RpcEnvironment;
use proxmox_schema as schema;
use anyhow::Error;
use serde_json::{json, Value};
@ -27,9 +28,9 @@ pub fn get_archive(archive_name: String) {
#[test]
fn get_archive_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Sync(&api_function_get_archive),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_get_archive),
&::proxmox_schema::ObjectSchema::new(
"Get an archive.",
&[("archive-name", false, &NAME_SCHEMA)],
),
@ -56,9 +57,9 @@ pub fn get_archive_2(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Va
#[test]
fn get_archive_2_schema_check() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Sync(&api_function_get_archive_2),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_get_archive_2),
&::proxmox_schema::ObjectSchema::new(
"Get an archive.",
&[("archive-name", false, &NAME_SCHEMA)],
),
@ -88,14 +89,14 @@ pub fn get_data(param: Value) -> Result<(), Error> {
#[test]
fn get_data_schema_test() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Sync(&api_function_get_data),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_get_data),
&::proxmox_schema::ObjectSchema::new(
"Get data.",
&[(
"data",
false,
&::proxmox::api::schema::ArraySchema::new("The data", &NAME_SCHEMA).schema(),
&::proxmox_schema::ArraySchema::new("The data", &NAME_SCHEMA).schema(),
)],
),
)

View File

@ -1,6 +1,6 @@
//! Test the automatic addition of integer limits.
use proxmox::api::schema::ApiType;
use proxmox_schema::ApiType;
use proxmox_api_macro::api;
/// An i16: -32768 to 32767.
@ -9,8 +9,8 @@ pub struct AnI16(i16);
#[test]
fn test_an_i16_schema() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::IntegerSchema::new("An i16: -32768 to 32767.")
const TEST_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::IntegerSchema::new("An i16: -32768 to 32767.")
.minimum(-32768)
.maximum(32767)
.schema();
@ -24,8 +24,8 @@ pub struct I16G50(i16);
#[test]
fn test_i16g50_schema() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::IntegerSchema::new("Already limited on one side.")
const TEST_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::IntegerSchema::new("Already limited on one side.")
.minimum(-50)
.maximum(32767)
.schema();
@ -39,8 +39,8 @@ pub struct AnI32(i32);
#[test]
fn test_an_i32_schema() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::IntegerSchema::new("An i32: -0x8000_0000 to 0x7fff_ffff.")
const TEST_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::IntegerSchema::new("An i32: -0x8000_0000 to 0x7fff_ffff.")
.minimum(-0x8000_0000)
.maximum(0x7fff_ffff)
.schema();
@ -54,8 +54,8 @@ pub struct AnU32(u32);
#[test]
fn test_an_u32_schema() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::IntegerSchema::new("Unsigned implies a minimum of zero.")
const TEST_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::IntegerSchema::new("Unsigned implies a minimum of zero.")
.minimum(0)
.maximum(0xffff_ffff)
.schema();
@ -69,8 +69,8 @@ pub struct AnI64(i64);
#[test]
fn test_an_i64_schema() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::IntegerSchema::new("An i64: this is left unlimited.").schema();
const TEST_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::IntegerSchema::new("An i64: this is left unlimited.").schema();
assert_eq!(TEST_SCHEMA, AnI64::API_SCHEMA);
}
@ -81,8 +81,8 @@ pub struct AnU64(u64);
#[test]
fn test_an_u64_schema() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::IntegerSchema::new("Unsigned implies a minimum of zero.")
const TEST_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::IntegerSchema::new("Unsigned implies a minimum of zero.")
.minimum(0)
.schema();

View File

@ -40,7 +40,7 @@ pub fn test_default_macro(value: Option<isize>) -> Result<isize, Error> {
}
struct RpcEnv;
impl proxmox::api::RpcEnvironment for RpcEnv {
impl proxmox_router::RpcEnvironment for RpcEnv {
fn result_attrib_mut(&mut self) -> &mut Value {
panic!("result_attrib_mut called");
}
@ -50,7 +50,7 @@ impl proxmox::api::RpcEnvironment for RpcEnv {
}
/// The environment type
fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
fn env_type(&self) -> proxmox_router::RpcEnvironmentType {
panic!("env_type called");
}

View File

@ -3,8 +3,9 @@
#![allow(dead_code)]
use proxmox::api::schema::{self, ApiType, EnumEntry};
use proxmox_api_macro::api;
use proxmox_schema as schema;
use proxmox_schema::{ApiType, EnumEntry};
use anyhow::Error;
use serde::Deserialize;
@ -23,13 +24,12 @@ pub struct OkString(String);
#[test]
fn ok_string() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::StringSchema::new("A string")
.format(&schema::ApiStringFormat::Enum(&[
EnumEntry::new("ok", "Ok"),
EnumEntry::new("not-ok", "Not OK"),
]))
.schema();
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::StringSchema::new("A string")
.format(&schema::ApiStringFormat::Enum(&[
EnumEntry::new("ok", "Ok"),
EnumEntry::new("not-ok", "Not OK"),
]))
.schema();
assert_eq!(TEST_SCHEMA, OkString::API_SCHEMA);
}
@ -45,26 +45,23 @@ pub struct TestStruct {
#[test]
fn test_struct() {
pub const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::ObjectSchema::new(
"An example of a simple struct type.",
&[
(
"another",
true,
&::proxmox::api::schema::StringSchema::new(
"An optional auto-derived value for testing:",
)
pub const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"An example of a simple struct type.",
&[
(
"another",
true,
&::proxmox_schema::StringSchema::new("An optional auto-derived value for testing:")
.schema(),
),
(
"test_string",
false,
&::proxmox::api::schema::StringSchema::new("A test string.").schema(),
),
],
)
.schema();
),
(
"test_string",
false,
&::proxmox_schema::StringSchema::new("A test string.").schema(),
),
],
)
.schema();
assert_eq!(TEST_SCHEMA, TestStruct::API_SCHEMA);
}
@ -84,21 +81,19 @@ pub struct RenamedStruct {
#[test]
fn renamed_struct() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::ObjectSchema::new(
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"An example of a struct with renamed fields.",
&[
(
"SomeOther",
true,
&::proxmox::api::schema::StringSchema::new(
"An optional auto-derived value for testing:",
)
.schema(),
&::proxmox_schema::StringSchema::new("An optional auto-derived value for testing:")
.schema(),
),
(
"test-string",
false,
&::proxmox::api::schema::StringSchema::new("A test string.").schema(),
&::proxmox_schema::StringSchema::new("A test string.").schema(),
),
],
)
@ -123,10 +118,10 @@ pub enum Selection {
#[test]
fn selection_test() {
const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::StringSchema::new(
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::StringSchema::new(
"A selection of either \'onekind\', \'another-kind\' or \'selection-number-three\'.",
)
.format(&::proxmox::api::schema::ApiStringFormat::Enum(&[
.format(&::proxmox_schema::ApiStringFormat::Enum(&[
EnumEntry::new("onekind", "The first kind."),
EnumEntry::new("another-kind", "Some other kind."),
EnumEntry::new("selection-number-three", "And yet another."),
@ -157,9 +152,9 @@ pub fn string_check(arg: Value, selection: Selection) -> Result<bool, Error> {
#[test]
fn string_check_schema_test() {
const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox::api::ApiHandler::Sync(&api_function_string_check),
&::proxmox::api::schema::ObjectSchema::new(
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_string_check),
&::proxmox_schema::ObjectSchema::new(
"Check a string.",
&[
("arg", false, &OkString::API_SCHEMA),
@ -167,9 +162,9 @@ fn string_check_schema_test() {
],
),
)
.returns(::proxmox::api::router::ReturnType::new(
.returns(::proxmox_schema::ReturnType::new(
true,
&::proxmox::api::schema::BooleanSchema::new("Whether the string was \"ok\".").schema(),
&::proxmox_schema::BooleanSchema::new("Whether the string was \"ok\".").schema(),
))
.protected(false);

View File

@ -1,7 +1,6 @@
#![allow(dead_code)]
use proxmox::api::api;
use proxmox::api::schema::{ApiType, Updater, UpdaterType};
use proxmox_schema::{api, ApiType, Updater, UpdaterType};
// Helpers for type checks:
struct AssertTypeEq<T>(T);
@ -41,23 +40,22 @@ pub struct Simple {
#[test]
fn test_simple() {
pub const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::ObjectSchema::new(
"An example of a simple struct type.",
&[
(
"one-field",
true,
&::proxmox::api::schema::StringSchema::new("A test string.").schema(),
),
(
"opt",
true,
&::proxmox::api::schema::StringSchema::new("Another test value.").schema(),
),
],
)
.schema();
pub const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"An example of a simple struct type.",
&[
(
"one-field",
true,
&::proxmox_schema::StringSchema::new("A test string.").schema(),
),
(
"opt",
true,
&::proxmox_schema::StringSchema::new("Another test value.").schema(),
),
],
)
.schema();
assert_eq!(TEST_SCHEMA, SimpleUpdater::API_SCHEMA);
}
@ -103,25 +101,24 @@ pub struct SuperComplex {
}
#[test]
fn test_super_complex() {
pub const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::ObjectSchema::new(
"One of the baaaad cases.",
&[
("custom", true, &<Option<Custom> as ApiType>::API_SCHEMA),
(
"extra",
true,
&::proxmox::api::schema::StringSchema::new("An extra field.").schema(),
),
(
"simple",
true,
//&<<Simple as UpdaterType>::Updater as ApiType>::API_SCHEMA,
&SimpleUpdater::API_SCHEMA,
),
],
)
.schema();
pub const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"One of the baaaad cases.",
&[
("custom", true, &<Option<Custom> as ApiType>::API_SCHEMA),
(
"extra",
true,
&::proxmox_schema::StringSchema::new("An extra field.").schema(),
),
(
"simple",
true,
//&<<Simple as UpdaterType>::Updater as ApiType>::API_SCHEMA,
&SimpleUpdater::API_SCHEMA,
),
],
)
.schema();
assert_eq!(TEST_SCHEMA, SuperComplexUpdater::API_SCHEMA);
}

View File

@ -90,3 +90,22 @@ macro_rules! offsetof {
unsafe { &(*(std::ptr::null::<$ty>())).$field as *const _ as usize }
};
}
/// Shortcut for generating an `&'static CStr`.
///
/// This takes a *string* (*not* a *byte-string*), appends a terminating zero, and calls
/// `CStr::from_bytes_with_nul_unchecked`.
///
/// Shortcut for:
/// ```no_run
/// let bytes = concat!("THE TEXT", "\0");
/// unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) }
/// # ;
/// ```
#[macro_export]
macro_rules! c_str {
($data:expr) => {{
let bytes = concat!($data, "\0");
unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) }
}};
}

30
proxmox-router/Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
name = "proxmox-router"
version = "1.0.0"
authors = ["Proxmox Support Team <support@proxmox.com>"]
edition = "2018"
license = "AGPL-3"
description = "proxmox API Router and CLI utilities"
exclude = [ "debian" ]
[dependencies]
anyhow = "1.0"
http = "0.2"
hyper = { version = "0.14", features = [ "full" ] }
percent-encoding = "2.1"
serde_json = "1.0"
unicode-width ="0.1.8"
# cli:
tokio = { version = "1.0", features = [], optional = true }
rustyline = { version = "7", optional = true }
libc = { version = "0.2", optional = true }
proxmox-lang = { path = "../proxmox-lang", version = "1.0" }
proxmox-schema = { path = "../proxmox-schema", version = "1.0" }
[features]
default = [ "cli" ]
cli = [ "libc", "rustyline", "tokio" ]
test-harness = [ "proxmox-schema/test-harness" ]

View File

@ -0,0 +1,5 @@
rust-proxmox-router (1.0.0-1) stable; urgency=medium
* initial split out of `librust-proxmox-dev`
-- Proxmox Support Team <support@proxmox.com> Wed, 06 Oct 2021 11:04:36 +0200

View File

@ -0,0 +1,137 @@
Source: rust-proxmox-router
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 24),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-http-0.2+default-dev <!nocheck>,
librust-hyper-0.14+default-dev <!nocheck>,
librust-hyper-0.14+full-dev <!nocheck>,
librust-libc-0.2+default-dev <!nocheck>,
librust-percent-encoding-2+default-dev (>= 2.1-~~) <!nocheck>,
librust-proxmox-lang-1+default-dev <!nocheck>,
librust-proxmox-schema-1+default-dev <!nocheck>,
librust-rustyline-7+default-dev <!nocheck>,
librust-serde-json-1+default-dev <!nocheck>,
librust-tokio-1+default-dev <!nocheck>,
librust-unicode-width-0.1+default-dev (>= 0.1.8-~~) <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.5.1
Vcs-Git: git://git.proxmox.com/git/proxmox.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
Rules-Requires-Root: no
Package: librust-proxmox-router-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-http-0.2+default-dev,
librust-hyper-0.14+default-dev,
librust-hyper-0.14+full-dev,
librust-percent-encoding-2+default-dev (>= 2.1-~~),
librust-proxmox-lang-1+default-dev,
librust-proxmox-schema-1+default-dev,
librust-serde-json-1+default-dev,
librust-unicode-width-0.1+default-dev (>= 0.1.8-~~)
Recommends:
librust-proxmox-router+cli-dev (= ${binary:Version})
Suggests:
librust-proxmox-router+libc-dev (= ${binary:Version}),
librust-proxmox-router+rustyline-dev (= ${binary:Version}),
librust-proxmox-router+test-harness-dev (= ${binary:Version}),
librust-proxmox-router+tokio-dev (= ${binary:Version})
Provides:
librust-proxmox-router-1-dev (= ${binary:Version}),
librust-proxmox-router-1.0-dev (= ${binary:Version}),
librust-proxmox-router-1.0.0-dev (= ${binary:Version})
Description: Proxmox API Router and CLI utilities - Rust source code
This package contains the source for the Rust proxmox-router crate, packaged by
debcargo for use with cargo and dh-cargo.
Package: librust-proxmox-router+cli-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-router-dev (= ${binary:Version}),
librust-libc-0.2+default-dev,
librust-rustyline-7+default-dev,
librust-tokio-1+default-dev
Provides:
librust-proxmox-router+default-dev (= ${binary:Version}),
librust-proxmox-router-1+cli-dev (= ${binary:Version}),
librust-proxmox-router-1+default-dev (= ${binary:Version}),
librust-proxmox-router-1.0+cli-dev (= ${binary:Version}),
librust-proxmox-router-1.0+default-dev (= ${binary:Version}),
librust-proxmox-router-1.0.0+cli-dev (= ${binary:Version}),
librust-proxmox-router-1.0.0+default-dev (= ${binary:Version})
Description: Proxmox API Router and CLI utilities - feature "cli" and 1 more
This metapackage enables feature "cli" for the Rust proxmox-router crate, by
pulling in any additional dependencies needed by that feature.
.
Additionally, this package also provides the "default" feature.
Package: librust-proxmox-router+libc-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-router-dev (= ${binary:Version}),
librust-libc-0.2+default-dev
Provides:
librust-proxmox-router-1+libc-dev (= ${binary:Version}),
librust-proxmox-router-1.0+libc-dev (= ${binary:Version}),
librust-proxmox-router-1.0.0+libc-dev (= ${binary:Version})
Description: Proxmox API Router and CLI utilities - feature "libc"
This metapackage enables feature "libc" for the Rust proxmox-router crate, by
pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-router+rustyline-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-router-dev (= ${binary:Version}),
librust-rustyline-7+default-dev
Provides:
librust-proxmox-router-1+rustyline-dev (= ${binary:Version}),
librust-proxmox-router-1.0+rustyline-dev (= ${binary:Version}),
librust-proxmox-router-1.0.0+rustyline-dev (= ${binary:Version})
Description: Proxmox API Router and CLI utilities - feature "rustyline"
This metapackage enables feature "rustyline" for the Rust proxmox-router crate,
by pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-router+test-harness-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-router-dev (= ${binary:Version}),
librust-proxmox-schema-1+test-harness-dev
Provides:
librust-proxmox-router-1+test-harness-dev (= ${binary:Version}),
librust-proxmox-router-1.0+test-harness-dev (= ${binary:Version}),
librust-proxmox-router-1.0.0+test-harness-dev (= ${binary:Version})
Description: Proxmox API Router and CLI utilities - feature "test-harness"
This metapackage enables feature "test-harness" for the Rust proxmox-router
crate, by pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-router+tokio-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-router-dev (= ${binary:Version}),
librust-tokio-1+default-dev
Provides:
librust-proxmox-router-1+tokio-dev (= ${binary:Version}),
librust-proxmox-router-1.0+tokio-dev (= ${binary:Version}),
librust-proxmox-router-1.0.0+tokio-dev (= ${binary:Version})
Description: Proxmox API Router and CLI utilities - feature "tokio"
This metapackage enables feature "tokio" for the Rust proxmox-router crate, by
pulling in any additional dependencies needed by that feature.

View File

@ -0,0 +1,16 @@
Copyright (C) 2021 Proxmox Server Solutions GmbH
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
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 <http://www.gnu.org/licenses/>.

View File

@ -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"

View File

@ -3,15 +3,16 @@ use serde_json::Value;
use std::cell::RefCell;
use std::sync::Arc;
use crate::api::format::*;
use crate::api::schema::*;
use crate::api::*;
use proxmox_schema::*;
use proxmox_schema::format::DocumentationFormat;
use super::environment::CliEnvironment;
use super::format::*;
use super::getopts;
use super::{completion::*, CliCommand, CliCommandMap, CommandLineInterface};
use super::{
generate_nested_usage, generate_usage_str, print_help, print_nested_usage_error,
print_simple_usage_error, CliCommand, CliCommandMap, CommandLineInterface,
};
use crate::{ApiFuture, ApiHandler, ApiMethod, RpcEnvironment};
/// Schema definition for ``--output-format`` parameter.
///
@ -349,7 +350,7 @@ fn prepare_cli_command(def: &CommandLineInterface) -> (String, Vec<String>) {
if !args.is_empty() {
if args[0] == "bashcomplete" {
print_bash_completion(&def);
def.print_bash_completion();
std::process::exit(0);
}

View File

@ -1,6 +1,9 @@
use super::*;
use std::collections::HashMap;
use crate::api::schema::*;
use proxmox_schema::*;
use super::help_command_def;
use super::{shellword_split_unclosed, CliCommand, CommandLineInterface, CompletionFunction};
fn record_done_argument(
done: &mut HashMap<String, String>,
@ -128,7 +131,7 @@ fn get_simple_completion(
// Try to parse all argumnets but last, record args already done
if args.len() > 1 {
let mut errors = ParameterError::new(); // we simply ignore any parsing errors here
let (data, _remaining) = getopts::parse_argument_list(
let (data, _remaining) = super::getopts::parse_argument_list(
&args[0..args.len() - 1],
cli_cmd.info.parameters,
&mut errors,
@ -174,156 +177,154 @@ fn get_simple_completion(
completions
}
fn get_help_completion(
def: &CommandLineInterface,
help_cmd: &CliCommand,
args: &[String],
) -> Vec<String> {
let mut done = HashMap::new();
impl CommandLineInterface {
fn get_help_completion(&self, help_cmd: &CliCommand, args: &[String]) -> Vec<String> {
let mut done = HashMap::new();
match self {
CommandLineInterface::Simple(_) => {
get_simple_completion(help_cmd, &mut done, &[], args)
}
CommandLineInterface::Nested(map) => {
if args.is_empty() {
let mut completions = Vec::new();
for cmd in map.commands.keys() {
completions.push(cmd.to_string());
}
return completions;
}
let first = &args[0];
if args.len() > 1 {
if let Some(sub_cmd) = map.commands.get(first) {
// do exact match here
return sub_cmd.get_help_completion(help_cmd, &args[1..]);
}
return Vec::new();
}
if first.starts_with('-') {
return get_simple_completion(help_cmd, &mut done, &[], args);
}
match def {
CommandLineInterface::Simple(_) => get_simple_completion(help_cmd, &mut done, &[], args),
CommandLineInterface::Nested(map) => {
if args.is_empty() {
let mut completions = Vec::new();
for cmd in map.commands.keys() {
completions.push(cmd.to_string());
if cmd.starts_with(first) {
completions.push(cmd.to_string());
}
}
return completions;
completions
}
let first = &args[0];
if args.len() > 1 {
if let Some(sub_cmd) = map.commands.get(first) {
// do exact match here
return get_help_completion(sub_cmd, help_cmd, &args[1..]);
}
return Vec::new();
}
if first.starts_with('-') {
return get_simple_completion(help_cmd, &mut done, &[], args);
}
let mut completions = Vec::new();
for cmd in map.commands.keys() {
if cmd.starts_with(first) {
completions.push(cmd.to_string());
}
}
completions
}
}
}
fn get_nested_completion(def: &CommandLineInterface, args: &[String]) -> Vec<String> {
match def {
CommandLineInterface::Simple(cli_cmd) => {
let mut done: HashMap<String, String> = HashMap::new();
cli_cmd.fixed_param.iter().for_each(|(key, value)| {
record_done_argument(&mut done, cli_cmd.info.parameters, &key, &value);
});
get_simple_completion(cli_cmd, &mut done, &cli_cmd.arg_param, args)
}
CommandLineInterface::Nested(map) => {
if args.is_empty() {
fn get_nested_completion(&self, args: &[String]) -> Vec<String> {
match self {
CommandLineInterface::Simple(cli_cmd) => {
let mut done: HashMap<String, String> = HashMap::new();
cli_cmd.fixed_param.iter().for_each(|(key, value)| {
record_done_argument(&mut done, cli_cmd.info.parameters, &key, &value);
});
get_simple_completion(cli_cmd, &mut done, &cli_cmd.arg_param, args)
}
CommandLineInterface::Nested(map) => {
if args.is_empty() {
let mut completions = Vec::new();
for cmd in map.commands.keys() {
completions.push(cmd.to_string());
}
return completions;
}
let first = &args[0];
if args.len() > 1 {
if let Some((_, sub_cmd)) = map.find_command(first) {
return sub_cmd.get_nested_completion(&args[1..]);
}
return Vec::new();
}
let mut completions = Vec::new();
for cmd in map.commands.keys() {
completions.push(cmd.to_string());
if cmd.starts_with(first) {
completions.push(cmd.to_string());
}
}
return completions;
completions
}
let first = &args[0];
if args.len() > 1 {
if let Some((_, sub_cmd)) = map.find_command(first) {
return get_nested_completion(sub_cmd, &args[1..]);
}
return Vec::new();
}
let mut completions = Vec::new();
for cmd in map.commands.keys() {
if cmd.starts_with(first) {
completions.push(cmd.to_string());
}
}
completions
}
}
}
/// Helper to generate bash completions.
///
/// This helper extracts the command line from environment variable
/// set by ``bash``, namely ``COMP_LINE`` and ``COMP_POINT``. This is
/// passed to ``get_completions()``. Returned values are printed to
/// ``stdout``.
pub fn print_bash_completion(def: &CommandLineInterface) {
let comp_point: usize = match std::env::var("COMP_POINT") {
Ok(val) => match usize::from_str_radix(&val, 10) {
Ok(i) => i,
/// Helper to generate bash completions.
///
/// This helper extracts the command line from environment variable
/// set by ``bash``, namely ``COMP_LINE`` and ``COMP_POINT``. This is
/// passed to ``get_completions()``. Returned values are printed to
/// ``stdout``.
pub fn print_bash_completion(&self) {
let comp_point: usize = match std::env::var("COMP_POINT") {
Ok(val) => match usize::from_str_radix(&val, 10) {
Ok(i) => i,
Err(_) => return,
},
Err(_) => return,
},
Err(_) => return,
};
};
let cmdline = match std::env::var("COMP_LINE") {
Ok(mut val) => {
if let Some((byte_pos, _)) = val.char_indices().nth(comp_point) {
val.truncate(byte_pos);
let cmdline = match std::env::var("COMP_LINE") {
Ok(mut val) => {
if let Some((byte_pos, _)) = val.char_indices().nth(comp_point) {
val.truncate(byte_pos);
}
val
}
val
Err(_) => return,
};
let (_start, completions) = self.get_completions(&cmdline, true);
for item in completions {
println!("{}", item);
}
Err(_) => return,
};
let (_start, completions) = super::get_completions(def, &cmdline, true);
for item in completions {
println!("{}", item);
}
}
/// Compute possible completions for a partial command
pub fn get_completions(
cmd_def: &CommandLineInterface,
line: &str,
skip_first: bool,
) -> (usize, Vec<String>) {
let (mut args, start) = match shellword_split_unclosed(line, false) {
(mut args, None) => {
args.push("".into());
(args, line.len())
}
(mut args, Some((start, arg, _quote))) => {
args.push(arg);
(args, start)
}
};
if skip_first {
if args.is_empty() {
return (0, Vec::new());
}
args.remove(0); // no need for program name
}
let completions = if !args.is_empty() && args[0] == "help" {
get_help_completion(cmd_def, &help_command_def(), &args[1..])
} else {
get_nested_completion(cmd_def, &args)
};
/// Compute possible completions for a partial command
pub fn get_completions(&self, line: &str, skip_first: bool) -> (usize, Vec<String>) {
let (mut args, start) = match shellword_split_unclosed(line, false) {
(mut args, None) => {
args.push("".into());
(args, line.len())
}
(mut args, Some((start, arg, _quote))) => {
args.push(arg);
(args, start)
}
};
(start, completions)
if skip_first {
if args.is_empty() {
return (0, Vec::new());
}
args.remove(0); // no need for program name
}
let completions = if !args.is_empty() && args[0] == "help" {
self.get_help_completion(&help_command_def(), &args[1..])
} else {
self.get_nested_completion(&args)
};
(start, completions)
}
}
#[cfg(test)]
mod test {
use anyhow::*;
use anyhow::Error;
use serde_json::Value;
use crate::api::{cli::*, schema::*, *};
use proxmox_schema::{BooleanSchema, ObjectSchema, StringSchema};
use crate::cli::{CliCommand, CliCommandMap, CommandLineInterface};
use crate::{ApiHandler, ApiMethod, RpcEnvironment};
fn dummy_method(
_param: Value,
@ -379,7 +380,7 @@ mod test {
let mut expect: Vec<String> = expect.iter().map(|s| s.to_string()).collect();
expect.sort();
let (completion_start, mut completions) = get_completions(cmd_def, line, false);
let (completion_start, mut completions) = cmd_def.get_completions(line, false);
completions.sort();
assert_eq!((start, expect), (completion_start, completions));

View File

@ -1,6 +1,6 @@
use serde_json::Value;
use crate::api::{RpcEnvironment, RpcEnvironmentType};
use crate::{RpcEnvironment, RpcEnvironmentType};
/// `RpcEnvironmet` implementation for command line tools
#[derive(Default)]

View File

@ -1,12 +1,13 @@
#![allow(clippy::match_bool)] // just no...
use serde_json::Value;
use std::collections::HashSet;
use crate::api::format::*;
use crate::api::router::ReturnType;
use crate::api::schema::*;
use serde_json::Value;
use proxmox_schema::*;
use proxmox_schema::format::{
get_property_description, get_schema_type_text, DocumentationFormat, ParameterDisplayStyle,
};
use super::{value_to_text, TableFormatOptions};
use super::{CliCommand, CliCommandMap, CommandLineInterface};

View File

@ -1,9 +1,9 @@
use std::collections::HashMap;
use anyhow::*;
use anyhow::format_err;
use serde_json::Value;
use crate::api::schema::*;
use proxmox_schema::*;
#[derive(Debug)]
enum RawArgument {
@ -237,6 +237,8 @@ fn test_boolean_arg() {
#[test]
fn test_argument_paramenter() {
use proxmox_schema::*;
const PARAMETERS: ObjectSchema = ObjectSchema::new(
"Parameters:",
&[

View File

@ -12,6 +12,10 @@
//! - Ability to create interactive commands (using ``rustyline``)
//! - Supports complex/nested commands
use std::collections::HashMap;
use crate::ApiMethod;
mod environment;
pub use environment::*;
@ -36,10 +40,6 @@ pub use command::*;
mod readline;
pub use readline::*;
use std::collections::HashMap;
use crate::api::ApiMethod;
/// Completion function for single parameters.
///
/// Completion functions gets the current parameter value, and should

View File

@ -34,7 +34,7 @@ impl rustyline::completion::Completer for CliHelper {
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let line = &line[..pos];
let (start, completions) = super::get_completions(&*self.cmd_def, line, false);
let (start, completions) = self.cmd_def.get_completions(line, false);
Ok((start, completions))
}

View File

@ -1,10 +1,11 @@
use std::io::Write;
use anyhow::*;
use anyhow::{bail, Error};
use serde_json::Value;
use unicode_width::UnicodeWidthStr;
use crate::api::schema::*;
use proxmox_lang::c_str;
use proxmox_schema::{ObjectSchemaType, Schema, SchemaPropertyEntry};
/// allows to configure the default output fromat using environment vars
pub const ENV_VAR_PROXMOX_OUTPUT_FORMAT: &str = "PROXMOX_OUTPUT_FORMAT";
@ -201,6 +202,21 @@ impl ColumnConfig {
}
}
/// Get the current size of the terminal (for stdout).
/// # Safety
///
/// uses unsafe call to tty_ioctl, see man tty_ioctl(2).
fn stdout_terminal_size() -> (usize, usize) {
let mut winsize = libc::winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
unsafe { libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut winsize) };
(winsize.ws_row as usize, winsize.ws_col as usize)
}
/// Table formatter configuration
#[derive(Default)]
pub struct TableFormatOptions {
@ -232,13 +248,13 @@ impl TableFormatOptions {
let is_tty = unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 };
if is_tty {
let (_rows, columns) = crate::sys::linux::tty::stdout_terminal_size();
let (_rows, columns) = stdout_terminal_size();
if columns > 0 {
me.columns = Some(columns);
}
}
let empty_cstr = crate::c_str!("");
let empty_cstr = c_str!("");
use std::ffi::CStr;
let encoding = unsafe {
@ -246,7 +262,7 @@ impl TableFormatOptions {
CStr::from_ptr(libc::nl_langinfo(libc::CODESET))
};
if encoding != crate::c_str!("UTF-8") {
if encoding != c_str!("UTF-8") {
me.ascii_delimiters = true;
}

View File

@ -28,8 +28,8 @@ impl fmt::Display for HttpError {
#[macro_export]
macro_rules! http_err {
($status:ident, $($fmt:tt)+) => {{
::anyhow::Error::from($crate::api::error::HttpError::new(
$crate::api::error::StatusCode::$status,
::anyhow::Error::from($crate::HttpError::new(
$crate::error::StatusCode::$status,
format!($($fmt)+)
))
}};

View File

@ -0,0 +1,88 @@
//! Module to generate and format API Documenation
use std::io::Write;
use anyhow::Error;
use proxmox_schema::format::*;
use proxmox_schema::ObjectSchemaType;
use crate::{ApiHandler, ApiMethod};
fn dump_method_definition(method: &str, path: &str, def: Option<&ApiMethod>) -> Option<String> {
let style = ParameterDisplayStyle::Config;
match def {
None => None,
Some(api_method) => {
let description = wrap_text("", "", &api_method.parameters.description(), 80);
let param_descr = dump_properties(&api_method.parameters, "", style, &[]);
let return_descr = dump_api_return_schema(&api_method.returns, style);
let mut method = method;
if let ApiHandler::AsyncHttp(_) = api_method.handler {
method = if method == "POST" { "UPLOAD" } else { method };
method = if method == "GET" { "DOWNLOAD" } else { method };
}
let res = format!(
"**{} {}**\n\n{}{}\n\n{}",
method, path, description, param_descr, return_descr
);
Some(res)
}
}
}
/// Generate ReST Documentaion for a complete API defined by a ``Router``.
pub fn dump_api(
output: &mut dyn Write,
router: &crate::Router,
path: &str,
mut pos: usize,
) -> Result<(), Error> {
use crate::SubRoute;
let mut cond_print = |x| -> Result<_, Error> {
if let Some(text) = x {
if pos > 0 {
writeln!(output, "-----\n")?;
}
writeln!(output, "{}", text)?;
pos += 1;
}
Ok(())
};
cond_print(dump_method_definition("GET", path, router.get))?;
cond_print(dump_method_definition("POST", path, router.post))?;
cond_print(dump_method_definition("PUT", path, router.put))?;
cond_print(dump_method_definition("DELETE", path, router.delete))?;
match &router.subroute {
None => return Ok(()),
Some(SubRoute::MatchAll { router, param_name }) => {
let sub_path = if path == "." {
format!("<{}>", param_name)
} else {
format!("{}/<{}>", path, param_name)
};
dump_api(output, router, &sub_path, pos)?;
}
Some(SubRoute::Map(dirmap)) => {
//let mut keys: Vec<&String> = map.keys().collect();
//keys.sort_unstable_by(|a, b| a.cmp(b));
for (key, sub_router) in dirmap.iter() {
let sub_path = if path == "." {
(*key).to_string()
} else {
format!("{}/{}", path, key)
};
dump_api(output, sub_router, &sub_path, pos)?;
}
}
}
Ok(())
}

25
proxmox-router/src/lib.rs Normal file
View File

@ -0,0 +1,25 @@
//! API Router and Command Line Interface utilities.
pub mod format;
#[cfg(feature = "cli")]
pub mod cli;
// this is public so the `http_err!` macro can access `http::StatusCode` through it
#[doc(hidden)]
pub mod error;
mod permission;
mod router;
mod rpc_environment;
#[doc(inline)]
pub use error::HttpError;
pub use permission::*;
pub use router::*;
pub use rpc_environment::{RpcEnvironment, RpcEnvironmentType};
// make list_subdirs_api_method! work without an explicit proxmox-schema dependency:
#[doc(hidden)]
pub use proxmox_schema::ObjectSchema as ListSubdirsObjectSchema;

View File

@ -197,10 +197,10 @@ fn check_api_permission_tail(
#[cfg(test)]
mod test {
use crate::api::permission::*;
use serde_json::{json, Value};
use crate::permission::*;
struct MockedUserInfo {
privs: Value,
groups: Value,

View File

@ -10,13 +10,10 @@ use hyper::Body;
use percent_encoding::percent_decode_str;
use serde_json::Value;
use crate::api::schema::{ObjectSchema, ParameterSchema, Schema};
use crate::api::RpcEnvironment;
use proxmox_schema::{ObjectSchema, ParameterSchema, ReturnType, Schema};
use super::Permission;
/// Deprecated reexport:
pub use super::schema::ReturnType;
use crate::RpcEnvironment;
/// A synchronous API handler gets a json Value as input and returns a json Value as output.
///
@ -24,8 +21,9 @@ pub use super::schema::ReturnType;
/// ```
/// # use anyhow::*;
/// # use serde_json::{json, Value};
/// # use proxmox::api::{*, schema::*};
/// #
/// use proxmox_router::{ApiHandler, ApiMethod, RpcEnvironment};
/// use proxmox_schema::ObjectSchema;
///
/// fn hello(
/// param: Value,
/// info: &ApiMethod,
@ -48,21 +46,21 @@ pub type ApiHandlerFn = &'static (dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironm
///
/// Returns a future Value.
/// ```
/// # use anyhow::*;
/// # use serde_json::{json, Value};
/// # use proxmox::api::{*, schema::*};
/// #
/// use futures::*;
/// use proxmox_router::{ApiFuture, ApiHandler, ApiMethod, RpcEnvironment};
/// use proxmox_schema::ObjectSchema;
///
///
/// fn hello_future<'a>(
/// param: Value,
/// info: &ApiMethod,
/// rpcenv: &'a mut dyn RpcEnvironment,
/// ) -> ApiFuture<'a> {
/// async move {
/// Box::pin(async move {
/// let data = json!("hello world!");
/// Ok(data)
/// }.boxed()
/// })
/// }
///
/// const API_METHOD_HELLO_FUTURE: ApiMethod = ApiMethod::new(
@ -81,13 +79,13 @@ pub type ApiFuture<'a> = Pin<Box<dyn Future<Output = Result<Value, anyhow::Error
/// They get low level access to request and response data. Use this
/// to implement custom upload/download functions.
/// ```
/// # use anyhow::*;
/// # use serde_json::{json, Value};
/// # use proxmox::api::{*, schema::*};
/// #
/// use futures::*;
/// use hyper::{Body, Response, http::request::Parts};
///
/// use proxmox_router::{ApiHandler, ApiMethod, ApiResponseFuture, RpcEnvironment};
/// use proxmox_schema::ObjectSchema;
///
/// fn low_level_hello(
/// parts: Parts,
/// req_body: Body,
@ -95,12 +93,12 @@ pub type ApiFuture<'a> = Pin<Box<dyn Future<Output = Result<Value, anyhow::Error
/// info: &ApiMethod,
/// rpcenv: Box<dyn RpcEnvironment>,
/// ) -> ApiResponseFuture {
/// async move {
/// Box::pin(async move {
/// let response = http::Response::builder()
/// .status(200)
/// .body(Body::from("Hello world!"))?;
/// Ok(response)
/// }.boxed()
/// })
/// }
///
/// const API_METHOD_LOW_LEVEL_HELLO: ApiMethod = ApiMethod::new(
@ -189,17 +187,17 @@ pub enum SubRoute {
#[macro_export]
macro_rules! list_subdirs_api_method {
($map:expr) => {
$crate::api::ApiMethod::new(
&$crate::api::ApiHandler::Sync( & |_, _, _| {
$crate::ApiMethod::new(
&$crate::ApiHandler::Sync( & |_, _, _| {
let index = ::serde_json::json!(
$map.iter().map(|s| ::serde_json::json!({ "subdir": s.0}))
.collect::<Vec<::serde_json::Value>>()
);
Ok(index)
}),
&$crate::api::schema::ObjectSchema::new("Directory index.", &[])
&$crate::ListSubdirsObjectSchema::new("Directory index.", &[])
.additional_properties(true)
).access(None, &$crate::api::Permission::Anybody)
).access(None, &$crate::Permission::Anybody)
}
}
@ -217,10 +215,10 @@ macro_rules! list_subdirs_api_method {
/// all `const fn(mut self, ..)` methods to configure them.
///
///```
/// # use anyhow::*;
/// # use serde_json::{json, Value};
/// # use proxmox::api::{*, schema::*};
/// #
/// use proxmox_router::{ApiHandler, ApiMethod, Router};
/// use proxmox_schema::ObjectSchema;
///
/// const API_METHOD_HELLO: ApiMethod = ApiMethod::new(
/// &ApiHandler::Sync(&|_, _, _| {
/// Ok(json!("Hello world!"))

View File

@ -1,9 +1,20 @@
use crate::tools::AsAny;
use std::any::Any;
use serde_json::Value;
/// Helper to get around `RpcEnvironment: Sized`
pub trait AsAny {
fn as_any(&self) -> &(dyn Any + Send);
}
impl<T: Any + Send> AsAny for T {
fn as_any(&self) -> &(dyn Any + Send) {
self
}
}
/// Abstract Interface for API methods to interact with the environment
pub trait RpcEnvironment: std::any::Any + AsAny + Send {
pub trait RpcEnvironment: Any + AsAny + Send {
/// Use this to pass additional result data. It is up to the environment
/// how the data is used.
fn result_attrib_mut(&mut self) -> &mut Value;

37
proxmox-schema/Cargo.toml Normal file
View File

@ -0,0 +1,37 @@
[package]
name = "proxmox-schema"
version = "1.0.0"
authors = ["Proxmox Support Team <support@proxmox.com>"]
edition = "2018"
license = "AGPL-3"
description = "proxmox api schema and validation"
exclude = [ "debian" ]
[dependencies]
anyhow = "1.0"
lazy_static = "1.4"
regex = "1.2"
serde = "1.0"
serde_json = "1.0"
textwrap = "0.11"
# the upid type needs this for 'getpid'
libc = { version = "0.2", optional = true }
nix = { version = "0.19", optional = true }
proxmox-api-macro = { path = "../proxmox-api-macro", optional = true, version = "1.0.0" }
[dev-dependencies]
url = "2.1"
serde = { version = "1.0", features = [ "derive" ] }
proxmox-api-macro = { path = "../proxmox-api-macro", version = "1.0.0" }
[features]
default = []
api-macro = ["proxmox-api-macro"]
upid-api-impl = [ "libc", "nix" ]
# Testing only
test-harness = []

View File

@ -0,0 +1,5 @@
rust-proxmox-schema (1.0.0-1) stable; urgency=medium
* initial split out of `librust-proxmox-dev`
-- Proxmox Support Team <support@proxmox.com> Wed, 06 Oct 2021 11:04:36 +0200

View File

@ -0,0 +1,118 @@
Source: rust-proxmox-schema
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 24),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-lazy-static-1+default-dev (>= 1.4-~~) <!nocheck>,
librust-regex-1+default-dev (>= 1.2-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-json-1+default-dev <!nocheck>,
librust-textwrap-0.11+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.5.1
Vcs-Git: git://git.proxmox.com/git/proxmox.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
Rules-Requires-Root: no
Package: librust-proxmox-schema-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-lazy-static-1+default-dev (>= 1.4-~~),
librust-regex-1+default-dev (>= 1.2-~~),
librust-serde-1+default-dev,
librust-serde-json-1+default-dev,
librust-textwrap-0.11+default-dev
Suggests:
librust-proxmox-schema+api-macro-dev (= ${binary:Version}),
librust-proxmox-schema+libc-dev (= ${binary:Version}),
librust-proxmox-schema+nix-dev (= ${binary:Version}),
librust-proxmox-schema+upid-api-impl-dev (= ${binary:Version})
Provides:
librust-proxmox-schema+default-dev (= ${binary:Version}),
librust-proxmox-schema+test-harness-dev (= ${binary:Version}),
librust-proxmox-schema-1-dev (= ${binary:Version}),
librust-proxmox-schema-1+default-dev (= ${binary:Version}),
librust-proxmox-schema-1+test-harness-dev (= ${binary:Version}),
librust-proxmox-schema-1.0-dev (= ${binary:Version}),
librust-proxmox-schema-1.0+default-dev (= ${binary:Version}),
librust-proxmox-schema-1.0+test-harness-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0+default-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0+test-harness-dev (= ${binary:Version})
Description: Proxmox api schema and validation - Rust source code
This package contains the source for the Rust proxmox-schema crate, packaged by
debcargo for use with cargo and dh-cargo.
Package: librust-proxmox-schema+api-macro-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-schema-dev (= ${binary:Version}),
librust-proxmox-api-macro-1+default-dev
Provides:
librust-proxmox-schema+proxmox-api-macro-dev (= ${binary:Version}),
librust-proxmox-schema-1+api-macro-dev (= ${binary:Version}),
librust-proxmox-schema-1+proxmox-api-macro-dev (= ${binary:Version}),
librust-proxmox-schema-1.0+api-macro-dev (= ${binary:Version}),
librust-proxmox-schema-1.0+proxmox-api-macro-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0+api-macro-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0+proxmox-api-macro-dev (= ${binary:Version})
Description: Proxmox api schema and validation - feature "api-macro" and 1 more
This metapackage enables feature "api-macro" for the Rust proxmox-schema crate,
by pulling in any additional dependencies needed by that feature.
.
Additionally, this package also provides the "proxmox-api-macro" feature.
Package: librust-proxmox-schema+libc-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-schema-dev (= ${binary:Version}),
librust-libc-0.2+default-dev
Provides:
librust-proxmox-schema-1+libc-dev (= ${binary:Version}),
librust-proxmox-schema-1.0+libc-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0+libc-dev (= ${binary:Version})
Description: Proxmox api schema and validation - feature "libc"
This metapackage enables feature "libc" for the Rust proxmox-schema crate, by
pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-schema+nix-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-schema-dev (= ${binary:Version}),
librust-nix-0.19+default-dev
Provides:
librust-proxmox-schema-1+nix-dev (= ${binary:Version}),
librust-proxmox-schema-1.0+nix-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0+nix-dev (= ${binary:Version})
Description: Proxmox api schema and validation - feature "nix"
This metapackage enables feature "nix" for the Rust proxmox-schema crate, by
pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-schema+upid-api-impl-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-schema-dev (= ${binary:Version}),
librust-libc-0.2+default-dev,
librust-nix-0.19+default-dev
Provides:
librust-proxmox-schema-1+upid-api-impl-dev (= ${binary:Version}),
librust-proxmox-schema-1.0+upid-api-impl-dev (= ${binary:Version}),
librust-proxmox-schema-1.0.0+upid-api-impl-dev (= ${binary:Version})
Description: Proxmox api schema and validation - feature "upid-api-impl"
This metapackage enables feature "upid-api-impl" for the Rust proxmox-schema
crate, by pulling in any additional dependencies needed by that feature.

View File

@ -0,0 +1,16 @@
Copyright (C) 2021 Proxmox Server Solutions GmbH
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
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 <http://www.gnu.org/licenses/>.

View File

@ -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"

View File

@ -2,11 +2,10 @@
///
/// This is meant to be used with an API-type tuple struct containing a single `String` like this:
///
/// ```ignore
/// # use proxmox::api::api;
/// # use proxmox::api::schema::ApiStringFormat;
/// ```
/// # use proxmox_schema::{api_string_type, ApiStringFormat};
/// # use proxmox_api_macro::api;
/// # const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat = ApiStringFormat::Enum(&[]);
/// use proxmox::api_string_type;
/// use serde::{Deserialize, Serialize};
///
/// api_string_type! {
@ -92,11 +91,12 @@ macro_rules! api_string_type {
}
/// Create an instance directly from a `String`, validating it using the API schema's
/// [`check_constraints`](::proxmox::api::schema::StringSchema::check_constraints())
/// [`check_constraints`](::proxmox_schema::StringSchema::check_constraints())
/// method.
pub fn from_string(inner: String) -> Result<Self, ::anyhow::Error> {
use $crate::ApiType;
match &Self::API_SCHEMA {
::proxmox::api::schema::Schema::String(s) => s.check_constraints(&inner)?,
$crate::Schema::String(s) => s.check_constraints(&inner)?,
_ => unreachable!(),
}
Ok(Self(inner))

View File

@ -30,8 +30,8 @@ impl std::ops::Deref for ConstRegexPattern {
/// Macro to generate a ConstRegexPattern
///
/// ```
/// # use proxmox::const_regex;
/// #
/// use proxmox_schema::const_regex;
///
/// const_regex!{
/// FILE_EXTENSION_REGEX = r".*\.([a-zA-Z]+)$";
/// pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$";
@ -43,11 +43,11 @@ macro_rules! const_regex {
$(#[$attr:meta])*
$vis:vis $name:ident = $regex:expr;
)+) => { $(
$(#[$attr])* $vis const $name: $crate::api::const_regex::ConstRegexPattern =
$crate::api::const_regex::ConstRegexPattern {
$(#[$attr])* $vis const $name: $crate::ConstRegexPattern =
$crate::ConstRegexPattern {
regex_string: $regex,
regex_obj: (|| -> &'static ::regex::Regex {
::lazy_static::lazy_static! {
$crate::semver_exempt::lazy_static! {
static ref SCHEMA: ::regex::Regex = ::regex::Regex::new($regex).unwrap();
}
&SCHEMA

View File

@ -5,21 +5,21 @@ use std::fmt;
use serde::de::{self, IntoDeserializer, Visitor};
use serde_json::Value;
use crate::api::schema::{ObjectSchemaType, Schema};
use crate::{ObjectSchemaType, Schema};
pub struct Error {
inner: anyhow::Error,
msg: String,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
fmt::Debug::fmt(&self.msg, f)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.inner, f)
fmt::Display::fmt(&self.msg, f)
}
}
@ -28,15 +28,15 @@ impl std::error::Error for Error {}
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self {
inner: anyhow::format_err!("{}", msg),
msg: msg.to_string(),
}
}
}
impl From<serde_json::Error> for Error {
fn from(inner: serde_json::Error) -> Self {
fn from(error: serde_json::Error) -> Self {
Error {
inner: inner.into(),
msg: error.to_string(),
}
}
}
@ -237,7 +237,7 @@ where
fn test_extraction() {
use serde::Deserialize;
use crate::api::schema::{ObjectSchema, StringSchema};
use crate::{ObjectSchema, StringSchema};
#[derive(Deserialize)]
struct Foo {

View File

@ -2,11 +2,7 @@
use anyhow::{bail, Error};
use std::io::Write;
use crate::api::{
router::ReturnType, schema::*, section_config::SectionConfig, ApiHandler, ApiMethod,
};
use crate::*;
/// Enumerate different styles to display parameters/properties.
#[derive(Copy, Clone, PartialEq)]
@ -74,128 +70,7 @@ fn test_wrap_text() {
assert_eq!(wrapped, expect);
}
/// Helper to format the type text
///
/// The result is a short string including important constraints, for
/// example ``<integer> (0 - N)``.
pub fn get_schema_type_text(schema: &Schema, _style: ParameterDisplayStyle) -> String {
match schema {
Schema::Null => String::from("<null>"), // should not happen
Schema::String(string_schema) => {
match string_schema {
StringSchema {
type_text: Some(type_text),
..
} => String::from(*type_text),
StringSchema {
format: Some(ApiStringFormat::Enum(variants)),
..
} => {
let list: Vec<String> =
variants.iter().map(|e| String::from(e.value)).collect();
list.join("|")
}
// displaying regex add more confision than it helps
//StringSchema { format: Some(ApiStringFormat::Pattern(const_regex)), .. } => {
// format!("/{}/", const_regex.regex_string)
//}
StringSchema {
format: Some(ApiStringFormat::PropertyString(sub_schema)),
..
} => get_property_string_type_text(sub_schema),
_ => String::from("<string>"),
}
}
Schema::Boolean(_) => String::from("<boolean>"),
Schema::Integer(integer_schema) => match (integer_schema.minimum, integer_schema.maximum) {
(Some(min), Some(max)) => format!("<integer> ({} - {})", min, max),
(Some(min), None) => format!("<integer> ({} - N)", min),
(None, Some(max)) => format!("<integer> (-N - {})", max),
_ => String::from("<integer>"),
},
Schema::Number(number_schema) => match (number_schema.minimum, number_schema.maximum) {
(Some(min), Some(max)) => format!("<number> ({} - {})", min, max),
(Some(min), None) => format!("<number> ({} - N)", min),
(None, Some(max)) => format!("<number> (-N - {})", max),
_ => String::from("<number>"),
},
Schema::Object(_) => String::from("<object>"),
Schema::Array(schema) => get_schema_type_text(schema.items, _style),
Schema::AllOf(_) => String::from("<object>"),
}
}
/// Helper to format an object property, including name, type and description.
pub fn get_property_description(
name: &str,
schema: &Schema,
style: ParameterDisplayStyle,
format: DocumentationFormat,
) -> String {
let type_text = get_schema_type_text(schema, style);
let (descr, default, extra) = match schema {
Schema::Null => ("null", None, None),
Schema::String(ref schema) => (schema.description, schema.default.map(|v| v.to_owned()), None),
Schema::Boolean(ref schema) => (schema.description, schema.default.map(|v| v.to_string()), None),
Schema::Integer(ref schema) => (schema.description, schema.default.map(|v| v.to_string()), None),
Schema::Number(ref schema) => (schema.description, schema.default.map(|v| v.to_string()), None),
Schema::Object(ref schema) => (schema.description, None, None),
Schema::AllOf(ref schema) => (schema.description, None, None),
Schema::Array(ref schema) => (schema.description, None, Some(String::from("Can be specified more than once."))),
};
let default_text = match default {
Some(text) => format!(" (default={})", text),
None => String::new(),
};
let descr = match extra {
Some(extra) => format!("{} {}", descr, extra),
None => String::from(descr),
};
if format == DocumentationFormat::ReST {
let mut text = match style {
ParameterDisplayStyle::Config => {
// reST definition list format
format!("``{}`` : ``{}{}``\n ", name, type_text, default_text)
}
ParameterDisplayStyle::ConfigSub => {
// reST definition list format
format!("``{}`` = ``{}{}``\n ", name, type_text, default_text)
}
ParameterDisplayStyle::Arg => {
// reST option list format
format!("``--{}`` ``{}{}``\n ", name, type_text, default_text)
}
ParameterDisplayStyle::Fixed => {
format!("``<{}>`` : ``{}{}``\n ", name, type_text, default_text)
}
};
text.push_str(&wrap_text("", " ", &descr, 80));
text.push('\n');
text
} else {
let display_name = match style {
ParameterDisplayStyle::Config => format!("{}:", name),
ParameterDisplayStyle::ConfigSub => format!("{}=", name),
ParameterDisplayStyle::Arg => format!("--{}", name),
ParameterDisplayStyle::Fixed => format!("<{}>", name),
};
let mut text = format!(" {:-10} {}{}", display_name, type_text, default_text);
let indent = " ";
text.push('\n');
text.push_str(&wrap_text(indent, indent, &descr, 80));
text
}
}
fn get_simply_type_text(schema: &Schema, list_enums: bool) -> String {
fn get_simple_type_text(schema: &Schema, list_enums: bool) -> String {
match schema {
Schema::Null => String::from("<null>"), // should not happen
Schema::Boolean(_) => String::from("<1|0>"),
@ -220,98 +95,10 @@ fn get_simply_type_text(schema: &Schema, list_enums: bool) -> String {
}
_ => String::from("<string>"),
},
_ => panic!("get_simply_type_text: expected simply type"),
_ => panic!("get_simple_type_text: expected simple type"),
}
}
fn get_object_type_text(object_schema: &ObjectSchema) -> String {
let mut parts = Vec::new();
let mut add_part = |name, optional, schema| {
let tt = get_simply_type_text(schema, false);
let text = if parts.is_empty() {
format!("{}={}", name, tt)
} else {
format!(",{}={}", name, tt)
};
if optional {
parts.push(format!("[{}]", text));
} else {
parts.push(text);
}
};
// add default key first
if let Some(ref default_key) = object_schema.default_key {
let (optional, schema) = object_schema.lookup(default_key).unwrap();
add_part(default_key, optional, schema);
}
// add required keys
for (name, optional, schema) in object_schema.properties {
if *optional {
continue;
}
if let Some(ref default_key) = object_schema.default_key {
if name == default_key {
continue;
}
}
add_part(name, *optional, schema);
}
// add options keys
for (name, optional, schema) in object_schema.properties {
if !*optional {
continue;
}
if let Some(ref default_key) = object_schema.default_key {
if name == default_key {
continue;
}
}
add_part(name, *optional, schema);
}
let mut type_text = String::new();
type_text.push('[');
type_text.push_str(&parts.join(" "));
type_text.push(']');
type_text
}
pub fn get_property_string_type_text(schema: &Schema) -> String {
match schema {
Schema::Object(object_schema) => get_object_type_text(object_schema),
Schema::Array(array_schema) => {
let item_type = get_simply_type_text(array_schema.items, true);
format!("[{}, ...]", item_type)
}
_ => panic!("get_property_string_type_text: expected array or object"),
}
}
/// Generate ReST Documentaion for enumeration.
pub fn dump_enum_properties(schema: &Schema) -> Result<String, Error> {
let mut res = String::new();
if let Schema::String(StringSchema {
format: Some(ApiStringFormat::Enum(variants)),
..
}) = schema
{
for item in variants.iter() {
res.push_str(&format!(":``{}``: ", item.value));
let descr = wrap_text("", " ", item.description, 80);
res.push_str(&descr);
res.push('\n');
}
return Ok(res);
}
bail!("dump_enum_properties failed - not an enum");
}
/// Generate ReST Documentaion for object properties
pub fn dump_properties(
param: &dyn ObjectSchemaType,
@ -395,7 +182,236 @@ pub fn dump_properties(
res
}
fn dump_api_return_schema(returns: &ReturnType, style: ParameterDisplayStyle) -> String {
/// Helper to format an object property, including name, type and description.
pub fn get_property_description(
name: &str,
schema: &Schema,
style: ParameterDisplayStyle,
format: DocumentationFormat,
) -> String {
let type_text = get_schema_type_text(schema, style);
let (descr, default, extra) = match schema {
Schema::Null => ("null", None, None),
Schema::String(ref schema) => (
schema.description,
schema.default.map(|v| v.to_owned()),
None,
),
Schema::Boolean(ref schema) => (
schema.description,
schema.default.map(|v| v.to_string()),
None,
),
Schema::Integer(ref schema) => (
schema.description,
schema.default.map(|v| v.to_string()),
None,
),
Schema::Number(ref schema) => (
schema.description,
schema.default.map(|v| v.to_string()),
None,
),
Schema::Object(ref schema) => (schema.description, None, None),
Schema::AllOf(ref schema) => (schema.description, None, None),
Schema::Array(ref schema) => (
schema.description,
None,
Some(String::from("Can be specified more than once.")),
),
};
let default_text = match default {
Some(text) => format!(" (default={})", text),
None => String::new(),
};
let descr = match extra {
Some(extra) => format!("{} {}", descr, extra),
None => String::from(descr),
};
if format == DocumentationFormat::ReST {
let mut text = match style {
ParameterDisplayStyle::Config => {
// reST definition list format
format!("``{}`` : ``{}{}``\n ", name, type_text, default_text)
}
ParameterDisplayStyle::ConfigSub => {
// reST definition list format
format!("``{}`` = ``{}{}``\n ", name, type_text, default_text)
}
ParameterDisplayStyle::Arg => {
// reST option list format
format!("``--{}`` ``{}{}``\n ", name, type_text, default_text)
}
ParameterDisplayStyle::Fixed => {
format!("``<{}>`` : ``{}{}``\n ", name, type_text, default_text)
}
};
text.push_str(&wrap_text("", " ", &descr, 80));
text.push('\n');
text
} else {
let display_name = match style {
ParameterDisplayStyle::Config => format!("{}:", name),
ParameterDisplayStyle::ConfigSub => format!("{}=", name),
ParameterDisplayStyle::Arg => format!("--{}", name),
ParameterDisplayStyle::Fixed => format!("<{}>", name),
};
let mut text = format!(" {:-10} {}{}", display_name, type_text, default_text);
let indent = " ";
text.push('\n');
text.push_str(&wrap_text(indent, indent, &descr, 80));
text
}
}
/// Helper to format the type text
///
/// The result is a short string including important constraints, for
/// example ``<integer> (0 - N)``.
pub fn get_schema_type_text(schema: &Schema, _style: ParameterDisplayStyle) -> String {
match schema {
Schema::Null => String::from("<null>"), // should not happen
Schema::String(string_schema) => {
match string_schema {
StringSchema {
type_text: Some(type_text),
..
} => String::from(*type_text),
StringSchema {
format: Some(ApiStringFormat::Enum(variants)),
..
} => {
let list: Vec<String> =
variants.iter().map(|e| String::from(e.value)).collect();
list.join("|")
}
// displaying regex add more confision than it helps
//StringSchema { format: Some(ApiStringFormat::Pattern(const_regex)), .. } => {
// format!("/{}/", const_regex.regex_string)
//}
StringSchema {
format: Some(ApiStringFormat::PropertyString(sub_schema)),
..
} => get_property_string_type_text(sub_schema),
_ => String::from("<string>"),
}
}
Schema::Boolean(_) => String::from("<boolean>"),
Schema::Integer(integer_schema) => match (integer_schema.minimum, integer_schema.maximum) {
(Some(min), Some(max)) => format!("<integer> ({} - {})", min, max),
(Some(min), None) => format!("<integer> ({} - N)", min),
(None, Some(max)) => format!("<integer> (-N - {})", max),
_ => String::from("<integer>"),
},
Schema::Number(number_schema) => match (number_schema.minimum, number_schema.maximum) {
(Some(min), Some(max)) => format!("<number> ({} - {})", min, max),
(Some(min), None) => format!("<number> ({} - N)", min),
(None, Some(max)) => format!("<number> (-N - {})", max),
_ => String::from("<number>"),
},
Schema::Object(_) => String::from("<object>"),
Schema::Array(schema) => get_schema_type_text(schema.items, _style),
Schema::AllOf(_) => String::from("<object>"),
}
}
pub fn get_property_string_type_text(schema: &Schema) -> String {
match schema {
Schema::Object(object_schema) => get_object_type_text(object_schema),
Schema::Array(array_schema) => {
let item_type = get_simple_type_text(array_schema.items, true);
format!("[{}, ...]", item_type)
}
_ => panic!("get_property_string_type_text: expected array or object"),
}
}
fn get_object_type_text(object_schema: &ObjectSchema) -> String {
let mut parts = Vec::new();
let mut add_part = |name, optional, schema| {
let tt = get_simple_type_text(schema, false);
let text = if parts.is_empty() {
format!("{}={}", name, tt)
} else {
format!(",{}={}", name, tt)
};
if optional {
parts.push(format!("[{}]", text));
} else {
parts.push(text);
}
};
// add default key first
if let Some(ref default_key) = object_schema.default_key {
let (optional, schema) = object_schema.lookup(default_key).unwrap();
add_part(default_key, optional, schema);
}
// add required keys
for (name, optional, schema) in object_schema.properties {
if *optional {
continue;
}
if let Some(ref default_key) = object_schema.default_key {
if name == default_key {
continue;
}
}
add_part(name, *optional, schema);
}
// add options keys
for (name, optional, schema) in object_schema.properties {
if !*optional {
continue;
}
if let Some(ref default_key) = object_schema.default_key {
if name == default_key {
continue;
}
}
add_part(name, *optional, schema);
}
let mut type_text = String::new();
type_text.push('[');
type_text.push_str(&parts.join(" "));
type_text.push(']');
type_text
}
/// Generate ReST Documentaion for enumeration.
pub fn dump_enum_properties(schema: &Schema) -> Result<String, Error> {
let mut res = String::new();
if let Schema::String(StringSchema {
format: Some(ApiStringFormat::Enum(variants)),
..
}) = schema
{
for item in variants.iter() {
res.push_str(&format!(":``{}``: ", item.value));
let descr = wrap_text("", " ", item.description, 80);
res.push_str(&descr);
res.push('\n');
}
return Ok(res);
}
bail!("dump_enum_properties failed - not an enum");
}
pub fn dump_api_return_schema(returns: &ReturnType, style: ParameterDisplayStyle) -> String {
let schema = &returns.schema;
let mut res = if returns.optional {
@ -447,114 +463,3 @@ fn dump_api_return_schema(returns: &ReturnType, style: ParameterDisplayStyle) ->
res
}
fn dump_method_definition(method: &str, path: &str, def: Option<&ApiMethod>) -> Option<String> {
let style = ParameterDisplayStyle::Config;
match def {
None => None,
Some(api_method) => {
let description = wrap_text("", "", &api_method.parameters.description(), 80);
let param_descr = dump_properties(&api_method.parameters, "", style, &[]);
let return_descr = dump_api_return_schema(&api_method.returns, style);
let mut method = method;
if let ApiHandler::AsyncHttp(_) = api_method.handler {
method = if method == "POST" { "UPLOAD" } else { method };
method = if method == "GET" { "DOWNLOAD" } else { method };
}
let res = format!(
"**{} {}**\n\n{}{}\n\n{}",
method, path, description, param_descr, return_descr
);
Some(res)
}
}
}
/// Generate ReST Documentaion for a complete API defined by a ``Router``.
pub fn dump_api(
output: &mut dyn Write,
router: &crate::api::Router,
path: &str,
mut pos: usize,
) -> Result<(), Error> {
use crate::api::SubRoute;
let mut cond_print = |x| -> Result<_, Error> {
if let Some(text) = x {
if pos > 0 {
writeln!(output, "-----\n")?;
}
writeln!(output, "{}", text)?;
pos += 1;
}
Ok(())
};
cond_print(dump_method_definition("GET", path, router.get))?;
cond_print(dump_method_definition("POST", path, router.post))?;
cond_print(dump_method_definition("PUT", path, router.put))?;
cond_print(dump_method_definition("DELETE", path, router.delete))?;
match &router.subroute {
None => return Ok(()),
Some(SubRoute::MatchAll { router, param_name }) => {
let sub_path = if path == "." {
format!("<{}>", param_name)
} else {
format!("{}/<{}>", path, param_name)
};
dump_api(output, router, &sub_path, pos)?;
}
Some(SubRoute::Map(dirmap)) => {
//let mut keys: Vec<&String> = map.keys().collect();
//keys.sort_unstable_by(|a, b| a.cmp(b));
for (key, sub_router) in dirmap.iter() {
let sub_path = if path == "." {
(*key).to_string()
} else {
format!("{}/{}", path, key)
};
dump_api(output, sub_router, &sub_path, pos)?;
}
}
}
Ok(())
}
/// Generate ReST Documentaion for ``SectionConfig``
pub fn dump_section_config(config: &SectionConfig) -> String {
let mut res = String::new();
let plugin_count = config.plugins().len();
for plugin in config.plugins().values() {
let name = plugin.type_name();
let properties = plugin.properties();
let skip = match plugin.id_property() {
Some(id) => vec![id],
None => Vec::new(),
};
if plugin_count > 1 {
let description = wrap_text("", "", properties.description(), 80);
res.push_str(&format!(
"\n**Section type** \'``{}``\': {}\n\n",
name, description
));
}
res.push_str(&dump_properties(
properties,
"",
ParameterDisplayStyle::Config,
&skip,
));
}
res
}

31
proxmox-schema/src/lib.rs Normal file
View File

@ -0,0 +1,31 @@
//! Proxmox schema module.
//!
//! This provides utilities to define APIs in a declarative way using
//! Schemas. Primary use case it to define REST/HTTP APIs. Another use case
//! is to define command line tools using Schemas. Finally, it is
//! possible to use schema definitions to derive configuration file
//! parsers.
#[cfg(feature = "api-macro")]
pub use proxmox_api_macro::api;
mod api_type_macros;
mod const_regex;
pub use const_regex::ConstRegexPattern;
pub mod de;
pub mod format;
mod schema;
pub use schema::*;
pub mod upid;
// const_regex uses lazy_static, but we otherwise don't need it, and don't want to force users to
// have to write it out in their Cargo.toml as dependency, so we add a hidden re-export here which
// is semver-exempt!
#[doc(hidden)]
pub mod semver_exempt {
pub use lazy_static::lazy_static;
}

View File

@ -8,9 +8,8 @@ use std::fmt;
use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
use url::form_urlencoded;
use crate::api::const_regex::ConstRegexPattern;
use crate::ConstRegexPattern;
/// Error type for schema validation
///
@ -50,13 +49,15 @@ impl ParameterError {
pub fn add_errors(&mut self, prefix: &str, err: Error) {
if let Some(param_err) = err.downcast_ref::<ParameterError>() {
for (sub_key, sub_err) in param_err.errors().iter() {
self.push(format!("{}/{}", prefix, sub_key), format_err!("{}", sub_err));
self.push(
format!("{}/{}", prefix, sub_key),
format_err!("{}", sub_err),
);
}
} else {
self.push(prefix.to_string(), err);
}
}
}
impl fmt::Display for ParameterError {
@ -623,8 +624,8 @@ impl Iterator for ObjectPropertyIterator {
/// `schema()` method to convert them into a `Schema`.
///
/// ```
/// # use proxmox::api::{*, schema::*};
/// #
/// use proxmox_schema::{Schema, BooleanSchema, IntegerSchema, ObjectSchema};
///
/// const SIMPLE_OBJECT: Schema = ObjectSchema::new(
/// "A very simple object with 2 properties",
/// &[ // this arrays needs to be storted by name!
@ -684,7 +685,8 @@ impl EnumEntry {
/// Simple list all possible values.
///
/// ```
/// # use proxmox::api::{*, schema::*};
/// use proxmox_schema::{ApiStringFormat, EnumEntry};
///
/// const format: ApiStringFormat = ApiStringFormat::Enum(&[
/// EnumEntry::new("vm", "A guest VM run via qemu"),
/// EnumEntry::new("ct", "A guest container run via lxc"),
@ -696,8 +698,8 @@ impl EnumEntry {
/// Use a regular expression to describe valid strings.
///
/// ```
/// # use proxmox::api::{*, schema::*};
/// # use proxmox::const_regex;
/// use proxmox_schema::{const_regex, ApiStringFormat};
///
/// const_regex! {
/// pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$";
/// }
@ -721,8 +723,9 @@ impl EnumEntry {
/// with simple properties (no nesting).
///
/// ```
/// # use proxmox::api::{*, schema::*};
/// #
/// use proxmox_schema::{ApiStringFormat, ArraySchema, IntegerSchema, Schema, StringSchema};
/// use proxmox_schema::{parse_simple_value, parse_property_string};
///
/// const PRODUCT_LIST_SCHEMA: Schema =
/// ArraySchema::new("Product List.", &IntegerSchema::new("Product ID").schema())
/// .min_length(1)
@ -961,7 +964,9 @@ fn do_parse_parameter_strings(
Err(err) => errors.push(key.into(), err),
}
}
_ => errors.push(key.into(), format_err!("expected array - type missmatch")),
_ => {
errors.push(key.into(), format_err!("expected array - type missmatch"))
}
}
}
_ => match parse_simple_value(value, prop_schema) {
@ -992,14 +997,20 @@ fn do_parse_parameter_strings(
_ => errors.push(key.into(), format_err!("expected array - type missmatch")),
}
} else {
errors.push(key.into(), format_err!("schema does not allow additional properties."));
errors.push(
key.into(),
format_err!("schema does not allow additional properties."),
);
}
}
if test_required && errors.is_empty() {
for (name, optional, _prop_schema) in schema.properties() {
if !(*optional) && params[name] == Value::Null {
errors.push(name.to_string(), format_err!("parameter is missing and it is not optional."));
errors.push(
name.to_string(),
format_err!("parameter is missing and it is not optional."),
);
}
}
}
@ -1011,21 +1022,6 @@ fn do_parse_parameter_strings(
}
}
/// Parse a `form_urlencoded` query string and verify with object schema
/// - `test_required`: is set, checks if all required properties are
/// present.
pub fn parse_query_string<T: Into<ParameterSchema>>(
query: &str,
schema: T,
test_required: bool,
) -> Result<Value, ParameterError> {
let param_list: Vec<(String, String)> = form_urlencoded::parse(query.as_bytes())
.into_owned()
.collect();
parse_parameter_strings(&param_list, schema.into(), test_required)
}
/// Verify JSON value with `schema`.
pub fn verify_json(data: &Value, schema: &Schema) -> Result<(), Error> {
match schema {
@ -1103,10 +1099,7 @@ pub fn verify_json_array(data: &Value, schema: &ArraySchema) -> Result<(), Error
}
/// Verify JSON value using an `ObjectSchema`.
pub fn verify_json_object(
data: &Value,
schema: &dyn ObjectSchemaType,
) -> Result<(), Error> {
pub fn verify_json_object(data: &Value, schema: &dyn ObjectSchemaType) -> Result<(), Error> {
let map = match data {
Value::Object(ref map) => map,
Value::Array(_) => bail!("Expected object - got array."),
@ -1128,13 +1121,19 @@ pub fn verify_json_object(
errors.add_errors(key, err);
};
} else if !additional_properties {
errors.push(key.to_string(), format_err!("schema does not allow additional properties."));
errors.push(
key.to_string(),
format_err!("schema does not allow additional properties."),
);
}
}
for (name, optional, _prop_schema) in schema.properties() {
if !(*optional) && data[name] == Value::Null {
errors.push(name.to_string(), format_err!("property is missing and it is not optional."));
errors.push(
name.to_string(),
format_err!("property is missing and it is not optional."),
);
}
}

295
proxmox-schema/src/upid.rs Normal file
View File

@ -0,0 +1,295 @@
use anyhow::{bail, Error};
use crate::{const_regex, ApiStringFormat, ApiType, Schema, StringSchema};
/// Unique Process/Task Identifier
///
/// We use this to uniquely identify worker task. UPIDs have a short
/// string repesentaion, which gives additional information about the
/// type of the task. for example:
/// ```text
/// UPID:{node}:{pid}:{pstart}:{task_id}:{starttime}:{worker_type}:{worker_id}:{userid}:
/// UPID:elsa:00004F37:0039E469:00000000:5CA78B83:garbage_collection::root@pam:
/// ```
/// Please note that we use tokio, so a single thread can run multiple
/// tasks.
// #[api] - manually implemented API type
#[derive(Debug, Clone)]
pub struct UPID {
/// The Unix PID
pub pid: i32, // really libc::pid_t, but we don't want this as a dependency for proxmox-schema
/// The Unix process start time from `/proc/pid/stat`
pub pstart: u64,
/// The task start time (Epoch)
pub starttime: i64,
/// The task ID (inside the process/thread)
pub task_id: usize,
/// Worker type (arbitrary ASCII string)
pub worker_type: String,
/// Worker ID (arbitrary ASCII string)
pub worker_id: Option<String>,
/// The authenticated entity who started the task
pub auth_id: String,
/// The node name.
pub node: String,
}
const_regex! {
pub PROXMOX_UPID_REGEX = concat!(
r"^UPID:(?P<node>[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?):(?P<pid>[0-9A-Fa-f]{8}):",
r"(?P<pstart>[0-9A-Fa-f]{8,9}):(?P<task_id>[0-9A-Fa-f]{8,16}):(?P<starttime>[0-9A-Fa-f]{8}):",
r"(?P<wtype>[^:\s]+):(?P<wid>[^:\s]*):(?P<authid>[^:\s]+):$"
);
}
pub const PROXMOX_UPID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PROXMOX_UPID_REGEX);
pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task Identifier")
.min_length("UPID:N:12345678:12345678:12345678:::".len())
.format(&PROXMOX_UPID_FORMAT)
.schema();
impl ApiType for UPID {
const API_SCHEMA: Schema = UPID_SCHEMA;
}
impl std::str::FromStr for UPID {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(cap) = PROXMOX_UPID_REGEX.captures(s) {
let worker_id = if cap["wid"].is_empty() {
None
} else {
let wid = unescape_id(&cap["wid"])?;
Some(wid)
};
Ok(UPID {
pid: i32::from_str_radix(&cap["pid"], 16).unwrap(),
pstart: u64::from_str_radix(&cap["pstart"], 16).unwrap(),
starttime: i64::from_str_radix(&cap["starttime"], 16).unwrap(),
task_id: usize::from_str_radix(&cap["task_id"], 16).unwrap(),
worker_type: cap["wtype"].to_string(),
worker_id,
auth_id: cap["authid"].to_string(),
node: cap["node"].to_string(),
})
} else {
bail!("unable to parse UPID '{}'", s);
}
}
}
impl std::fmt::Display for UPID {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let wid = if let Some(ref id) = self.worker_id {
escape_id(id)
} else {
String::new()
};
// Note: pstart can be > 32bit if uptime > 497 days, so this can result in
// more that 8 characters for pstart
write!(
f,
"UPID:{}:{:08X}:{:08X}:{:08X}:{:08X}:{}:{}:{}:",
self.node,
self.pid,
self.pstart,
self.task_id,
self.starttime,
self.worker_type,
wid,
self.auth_id
)
}
}
impl serde::Serialize for UPID {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&ToString::to_string(self))
}
}
impl<'de> serde::Deserialize<'de> for UPID {
fn deserialize<D>(deserializer: D) -> Result<UPID, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ForwardToStrVisitor;
impl<'a> serde::de::Visitor<'a> for ForwardToStrVisitor {
type Value = UPID;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid UPID")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<UPID, E> {
v.parse::<UPID>().map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self)
})
}
}
deserializer.deserialize_str(ForwardToStrVisitor)
}
}
// the following two are copied as they're the only `proxmox-systemd` dependencies in this crate,
// and this crate has MUCH fewer dependencies without it
/// non-path systemd-unit compatible escaping
fn escape_id(unit: &str) -> String {
use std::fmt::Write;
let mut escaped = String::new();
for (i, &c) in unit.as_bytes().iter().enumerate() {
if c == b'/' {
escaped.push('-');
} else if (i == 0 && c == b'.')
|| !matches!(c, b'_' | b'.' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z')
{
// unwrap: writing to a String
write!(escaped, "\\x{:02x}", c).unwrap();
} else {
escaped.push(char::from(c));
}
}
escaped
}
fn hex_digit(d: u8) -> Result<u8, Error> {
match d {
b'0'..=b'9' => Ok(d - b'0'),
b'A'..=b'F' => Ok(d - b'A' + 10),
b'a'..=b'f' => Ok(d - b'a' + 10),
_ => bail!("got invalid hex digit"),
}
}
/// systemd-unit compatible escaping
fn unescape_id(text: &str) -> Result<String, Error> {
let mut i = text.as_bytes();
let mut data: Vec<u8> = Vec::new();
loop {
if i.is_empty() {
break;
}
let next = i[0];
if next == b'\\' {
if i.len() < 4 || i[1] != b'x' {
bail!("error in escape sequence");
}
let h1 = hex_digit(i[2])?;
let h0 = hex_digit(i[3])?;
data.push(h1 << 4 | h0);
i = &i[4..]
} else if next == b'-' {
data.push(b'/');
i = &i[1..]
} else {
data.push(next);
i = &i[1..]
}
}
let text = String::from_utf8(data)?;
Ok(text)
}
#[cfg(feature = "upid-api-impl")]
mod upid_impl {
use std::sync::atomic::{AtomicUsize, Ordering};
use anyhow::{bail, format_err, Error};
use super::UPID;
impl UPID {
/// Create a new UPID
pub fn new(
worker_type: &str,
worker_id: Option<String>,
auth_id: String,
) -> Result<Self, Error> {
let pid = unsafe { libc::getpid() };
let bad: &[_] = &['/', ':', ' '];
if worker_type.contains(bad) {
bail!("illegal characters in worker type '{}'", worker_type);
}
if auth_id.contains(bad) {
bail!("illegal characters in auth_id '{}'", auth_id);
}
static WORKER_TASK_NEXT_ID: AtomicUsize = AtomicUsize::new(0);
let task_id = WORKER_TASK_NEXT_ID.fetch_add(1, Ordering::SeqCst);
Ok(UPID {
pid,
pstart: get_pid_start(pid)?,
starttime: epoch_i64(),
task_id,
worker_type: worker_type.to_owned(),
worker_id,
auth_id,
node: nix::sys::utsname::uname()
.nodename()
.split('.')
.next()
.ok_or_else(|| format_err!("failed to get nodename from uname()"))?
.to_owned(),
})
}
}
fn get_pid_start(pid: libc::pid_t) -> Result<u64, Error> {
let statstr = String::from_utf8(std::fs::read(format!("/proc/{}/stat", pid))?)?;
let cmdend = statstr
.rfind(')')
.ok_or_else(|| format_err!("missing ')' in /proc/PID/stat"))?;
let starttime = statstr[cmdend + 1..]
.trim_start()
.split_ascii_whitespace()
.nth(19)
.ok_or_else(|| format_err!("failed to find starttime in /proc/{}/stat", pid))?;
starttime.parse().map_err(|err| {
format_err!(
"failed to parse starttime from /proc/{}/stat ({:?}): {}",
pid,
starttime,
err,
)
})
}
// Copied as this is the only `proxmox-time` dependency in this crate
// and this crate has MUCH fewer dependencies without it
fn epoch_i64() -> i64 {
use std::convert::TryFrom;
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now();
if now > UNIX_EPOCH {
i64::try_from(now.duration_since(UNIX_EPOCH).unwrap().as_secs())
.expect("epoch_i64: now is too large")
} else {
-i64::try_from(UNIX_EPOCH.duration_since(now).unwrap().as_secs())
.expect("epoch_i64: now is too small")
}
}
}

View File

@ -1,6 +1,20 @@
use anyhow::bail;
use serde_json::Value;
use url::form_urlencoded;
use crate::api::schema::*;
use proxmox_schema::*;
fn parse_query_string<T: Into<ParameterSchema>>(
query: &str,
schema: T,
test_required: bool,
) -> Result<Value, ParameterError> {
let param_list: Vec<(String, String)> = form_urlencoded::parse(query.as_bytes())
.into_owned()
.collect();
parse_parameter_strings(&param_list, schema.into(), test_required)
}
#[test]
fn test_schema1() {

View File

@ -1,7 +1,7 @@
use anyhow::{bail, Error};
use serde_json::{json, Value};
use crate::api::schema::*;
use proxmox_schema::*;
static STRING_SCHEMA: Schema = StringSchema::new("A test string").schema();
@ -46,7 +46,7 @@ fn compare_error(
None => bail!("unable to downcast error: {}", err),
};
let result = crate::try_block!({
let result = (move || {
let errors = err.errors();
if errors.len() != expected.len() {
@ -64,7 +64,7 @@ fn compare_error(
}
Ok(())
});
})();
if result.is_err() {
println!("GOT: {:?}", err);

View File

@ -25,40 +25,11 @@ endian_trait = { version = "0.6", features = ["arrays"] }
regex = "1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
#valgrind_request = { git = "https://github.com/edef1c/libvalgrind_request", version = "1.1.0", optional = true }
# libc, nix, lazy_static
# sys module:
# libc, nix, lazy_static
# api module:
bytes = "1.0"
futures = { version = "0.3", optional = true }
http = "0.2"
hyper = { version = "0.14", features = [ "full" ], optional = true }
percent-encoding = "2.1"
rustyline = "7"
textwrap = "0.11"
tokio = { version = "1.0", features = [], optional = true }
tokio-stream = { version = "0.1.1", optional = true }
url = "2.1"
#regex, serde, serde_json
# Macro crates:
proxmox-api-macro = { path = "../proxmox-api-macro", optional = true, version = "0.5.1" }
proxmox-sortable-macro = { path = "../proxmox-sortable-macro", optional = true, version = "0.1.1" }
[features]
default = [ "cli", "router" ]
default = []
sortable-macro = ["proxmox-sortable-macro"]
# api:
api-macro = ["proxmox-api-macro"]
test-harness = []
cli = [ "router", "hyper", "tokio" ]
router = [ "futures", "hyper", "tokio" ]
examples = ["tokio/macros"]
# tools:
#valgrind = ["proxmox-tools/valgrind"]

View File

@ -1,57 +0,0 @@
//! Proxmox API module.
//!
//! This provides utilities to define APIs in a declarative way using
//! Schemas. Primary use case it to define REST/HTTP APIs. Another use case
//! is to define command line tools using Schemas. Finally, it is
//! possible to use schema definitions to derive configuration file
//! parsers.
#[cfg(feature = "api-macro")]
pub use proxmox_api_macro::{api, router};
#[macro_use]
mod api_type_macros;
#[doc(hidden)]
pub mod const_regex;
#[doc(hidden)]
pub mod error;
pub mod schema;
pub mod section_config;
mod permission;
pub use permission::*;
#[doc(inline)]
pub use const_regex::ConstRegexPattern;
#[doc(inline)]
pub use error::HttpError;
#[cfg(any(feature = "router", feature = "cli"))]
#[doc(hidden)]
pub mod rpc_environment;
#[cfg(any(feature = "router", feature = "cli"))]
#[doc(inline)]
pub use rpc_environment::{RpcEnvironment, RpcEnvironmentType};
#[cfg(feature = "router")]
pub mod format;
#[cfg(feature = "router")]
#[doc(hidden)]
pub mod router;
#[cfg(feature = "router")]
#[doc(inline)]
pub use router::{
ApiFuture, ApiHandler, ApiMethod, ApiResponseFuture, Router, SubRoute, SubdirMap,
};
#[cfg(feature = "cli")]
pub mod cli;
pub mod de;
pub mod upid;

View File

@ -883,3 +883,36 @@ lvmthin: local-lvm2
assert_eq!(raw, created);
}
/// Generate ReST Documentaion for ``SectionConfig``
pub fn dump_section_config(config: &SectionConfig) -> String {
let mut res = String::new();
let plugin_count = config.plugins().len();
for plugin in config.plugins().values() {
let name = plugin.type_name();
let properties = plugin.properties();
let skip = match plugin.id_property() {
Some(id) => vec![id],
None => Vec::new(),
};
if plugin_count > 1 {
let description = wrap_text("", "", properties.description(), 80);
res.push_str(&format!(
"\n**Section type** \'``{}``\': {}\n\n",
name, description
));
}
res.push_str(&dump_properties(
properties,
"",
ParameterDisplayStyle::Config,
&skip,
));
}
res
}

View File

@ -1,148 +0,0 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use anyhow::{bail, Error};
use crate::api::schema::{ApiStringFormat, ApiType, Schema, StringSchema};
use crate::const_regex;
use crate::sys::linux::procfs;
/// Unique Process/Task Identifier
///
/// We use this to uniquely identify worker task. UPIDs have a short
/// string repesentaion, which gives additional information about the
/// type of the task. for example:
/// ```text
/// UPID:{node}:{pid}:{pstart}:{task_id}:{starttime}:{worker_type}:{worker_id}:{userid}:
/// UPID:elsa:00004F37:0039E469:00000000:5CA78B83:garbage_collection::root@pam:
/// ```
/// Please note that we use tokio, so a single thread can run multiple
/// tasks.
// #[api] - manually implemented API type
#[derive(Debug, Clone)]
pub struct UPID {
/// The Unix PID
pub pid: libc::pid_t,
/// The Unix process start time from `/proc/pid/stat`
pub pstart: u64,
/// The task start time (Epoch)
pub starttime: i64,
/// The task ID (inside the process/thread)
pub task_id: usize,
/// Worker type (arbitrary ASCII string)
pub worker_type: String,
/// Worker ID (arbitrary ASCII string)
pub worker_id: Option<String>,
/// The authenticated entity who started the task
pub auth_id: String,
/// The node name.
pub node: String,
}
crate::forward_serialize_to_display!(UPID);
crate::forward_deserialize_to_from_str!(UPID);
const_regex! {
pub PROXMOX_UPID_REGEX = concat!(
r"^UPID:(?P<node>[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?):(?P<pid>[0-9A-Fa-f]{8}):",
r"(?P<pstart>[0-9A-Fa-f]{8,9}):(?P<task_id>[0-9A-Fa-f]{8,16}):(?P<starttime>[0-9A-Fa-f]{8}):",
r"(?P<wtype>[^:\s]+):(?P<wid>[^:\s]*):(?P<authid>[^:\s]+):$"
);
}
pub const PROXMOX_UPID_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&PROXMOX_UPID_REGEX);
pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task Identifier")
.min_length("UPID:N:12345678:12345678:12345678:::".len())
.format(&PROXMOX_UPID_FORMAT)
.schema();
impl ApiType for UPID {
const API_SCHEMA: Schema = UPID_SCHEMA;
}
impl UPID {
/// Create a new UPID
pub fn new(
worker_type: &str,
worker_id: Option<String>,
auth_id: String,
) -> Result<Self, Error> {
let pid = unsafe { libc::getpid() };
let bad: &[_] = &['/', ':', ' '];
if worker_type.contains(bad) {
bail!("illegal characters in worker type '{}'", worker_type);
}
if auth_id.contains(bad) {
bail!("illegal characters in auth_id '{}'", auth_id);
}
static WORKER_TASK_NEXT_ID: AtomicUsize = AtomicUsize::new(0);
let task_id = WORKER_TASK_NEXT_ID.fetch_add(1, Ordering::SeqCst);
Ok(UPID {
pid,
pstart: procfs::PidStat::read_from_pid(nix::unistd::Pid::from_raw(pid))?.starttime,
starttime: crate::tools::time::epoch_i64(),
task_id,
worker_type: worker_type.to_owned(),
worker_id,
auth_id,
node: crate::tools::nodename().to_owned(),
})
}
}
impl std::str::FromStr for UPID {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(cap) = PROXMOX_UPID_REGEX.captures(s) {
let worker_id = if cap["wid"].is_empty() {
None
} else {
let wid = crate::tools::systemd::unescape_unit(&cap["wid"])?;
Some(wid)
};
Ok(UPID {
pid: i32::from_str_radix(&cap["pid"], 16).unwrap(),
pstart: u64::from_str_radix(&cap["pstart"], 16).unwrap(),
starttime: i64::from_str_radix(&cap["starttime"], 16).unwrap(),
task_id: usize::from_str_radix(&cap["task_id"], 16).unwrap(),
worker_type: cap["wtype"].to_string(),
worker_id,
auth_id: cap["authid"].to_string(),
node: cap["node"].to_string(),
})
} else {
bail!("unable to parse UPID '{}'", s);
}
}
}
impl std::fmt::Display for UPID {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let wid = if let Some(ref id) = self.worker_id {
crate::tools::systemd::escape_unit(id, false)
} else {
String::new()
};
// Note: pstart can be > 32bit if uptime > 497 days, so this can result in
// more that 8 characters for pstart
write!(f, "UPID:{}:{:08X}:{:08X}:{:08X}:{:08X}:{}:{}:{}:",
self.node, self.pid, self.pstart, self.task_id, self.starttime, self.worker_type, wid, self.auth_id)
}
}

View File

@ -4,7 +4,6 @@
#[macro_use]
pub mod serde_macros;
pub mod api;
pub mod sys;
pub mod tools;

View File

@ -64,22 +64,3 @@ macro_rules! c_try {
$crate::c_result!($expr)?
}};
}
/// Shortcut for generating an `&'static CStr`.
///
/// This takes a *string* (*not* a *byte-string*), appends a terminating zero, and calls
/// `CStr::from_bytes_with_nul_unchecked`.
///
/// Shortcut for:
/// ```no_run
/// let bytes = concat!("THE TEXT", "\0");
/// unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) }
/// # ;
/// ```
#[macro_export]
macro_rules! c_str {
($data:expr) => {{
let bytes = concat!($data, "\0");
unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) }
}};
}

View File

@ -1,14 +0,0 @@
use std::any::Any;
/// An easy way to convert types to Any
///
/// Mostly useful to downcast trait objects (see RpcEnvironment).
pub trait AsAny {
fn as_any(&self) -> &dyn Any;
}
impl<T: Any> AsAny for T {
fn as_any(&self) -> &dyn Any {
self
}
}

View File

@ -5,7 +5,6 @@ use std::fmt;
use anyhow::*;
use lazy_static::lazy_static;
pub mod as_any;
pub mod common_regex;
pub mod email;
pub mod fd;
@ -15,9 +14,6 @@ pub mod parse;
pub mod serde;
pub mod systemd;
#[doc(inline)]
pub use as_any::AsAny;
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
/// Helper to provide a `Display` for arbitrary byte slices.