add proxmox-schema and proxmox-router crates
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
01a8b6f1bf
commit
41f3fdfeb9
@ -6,6 +6,8 @@ members = [
|
||||
"proxmox-http",
|
||||
"proxmox-io",
|
||||
"proxmox-lang",
|
||||
"proxmox-router",
|
||||
"proxmox-schema",
|
||||
"proxmox-sortable-macro",
|
||||
"proxmox-tfa",
|
||||
"proxmox-time",
|
||||
|
2
Makefile
2
Makefile
@ -7,6 +7,8 @@ CRATES = \
|
||||
proxmox-http \
|
||||
proxmox-io \
|
||||
proxmox-lang \
|
||||
proxmox-router \
|
||||
proxmox-schema \
|
||||
proxmox-sortable-macro \
|
||||
proxmox-tfa \
|
||||
proxmox-time \
|
||||
|
@ -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)!
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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>;
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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() {
|
||||
|
@ -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(),
|
||||
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
)],
|
||||
),
|
||||
)
|
||||
|
@ -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(),
|
||||
)],
|
||||
),
|
||||
)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
30
proxmox-router/Cargo.toml
Normal 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" ]
|
5
proxmox-router/debian/changelog
Normal file
5
proxmox-router/debian/changelog
Normal 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
|
137
proxmox-router/debian/control
Normal file
137
proxmox-router/debian/control
Normal 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.
|
16
proxmox-router/debian/copyright
Normal file
16
proxmox-router/debian/copyright
Normal 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/>.
|
7
proxmox-router/debian/debcargo.toml
Normal file
7
proxmox-router/debian/debcargo.toml
Normal 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"
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
@ -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)]
|
@ -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};
|
@ -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:",
|
||||
&[
|
@ -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
|
@ -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))
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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)+)
|
||||
))
|
||||
}};
|
88
proxmox-router/src/format.rs
Normal file
88
proxmox-router/src/format.rs
Normal 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
25
proxmox-router/src/lib.rs
Normal 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;
|
@ -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,
|
@ -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!"))
|
@ -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
37
proxmox-schema/Cargo.toml
Normal 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 = []
|
5
proxmox-schema/debian/changelog
Normal file
5
proxmox-schema/debian/changelog
Normal 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
|
118
proxmox-schema/debian/control
Normal file
118
proxmox-schema/debian/control
Normal 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.
|
16
proxmox-schema/debian/copyright
Normal file
16
proxmox-schema/debian/copyright
Normal 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/>.
|
7
proxmox-schema/debian/debcargo.toml
Normal file
7
proxmox-schema/debian/debcargo.toml
Normal 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"
|
@ -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))
|
@ -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
|
@ -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 {
|
@ -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
31
proxmox-schema/src/lib.rs
Normal 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;
|
||||
}
|
@ -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(¶m_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
295
proxmox-schema/src/upid.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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(¶m_list, schema.into(), test_required)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_schema1() {
|
@ -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);
|
@ -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"]
|
||||
|
@ -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;
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
#[macro_use]
|
||||
pub mod serde_macros;
|
||||
|
||||
pub mod api;
|
||||
pub mod sys;
|
||||
pub mod tools;
|
||||
|
||||
|
@ -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()) }
|
||||
}};
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user