1 use anyhow
::{bail, Error}
;
2 use serde_json
::{json, Value}
;
4 use proxmox_router
::{ApiAccess, ApiHandler, ApiMethod, Permission, Router, SubRoute}
;
5 use proxmox_schema
::format
::{dump_enum_properties, get_property_string_type_text}
;
6 use proxmox_schema
::{ApiStringFormat, ApiType, ObjectSchemaType, Schema}
;
7 use proxmox_section_config
::dump_section_config
;
9 use pbs_api_types
::PRIVILEGES
;
11 use proxmox_backup
::api2
;
13 fn get_args() -> (String
, Vec
<String
>) {
15 let mut args
= std
::env
::args();
16 let prefix
= args
.next().unwrap();
17 let prefix
= prefix
.rsplit('
/'
).next().unwrap().to_string(); // without path
18 let args
: Vec
<String
> = args
.collect();
23 fn main() -> Result
<(), Error
> {
25 let (_prefix
, args
) = get_args();
28 bail
!("missing arguments");
31 for arg
in args
.iter() {
32 let text
= match arg
.as_ref() {
33 "apidata.js" => generate_api_tree(),
34 "datastore.cfg" => dump_section_config(&pbs_config
::datastore
::CONFIG
),
35 "tape.cfg" => dump_section_config(&pbs_config
::drive
::CONFIG
),
36 "tape-job.cfg" => dump_section_config(&pbs_config
::tape_job
::CONFIG
),
37 "user.cfg" => dump_section_config(&pbs_config
::user
::CONFIG
),
38 "remote.cfg" => dump_section_config(&pbs_config
::remote
::CONFIG
),
39 "sync.cfg" => dump_section_config(&pbs_config
::sync
::CONFIG
),
40 "verification.cfg" => dump_section_config(&pbs_config
::verify
::CONFIG
),
41 "media-pool.cfg" => dump_section_config(&pbs_config
::media_pool
::CONFIG
),
42 "config::acl::Role" => dump_enum_properties(&pbs_api_types
::Role
::API_SCHEMA
)?
,
43 _
=> bail
!("docgen: got unknown type"),
51 fn generate_api_tree() -> String
{
53 let mut tree
= Vec
::new();
55 let mut data
= dump_api_schema(& api2
::ROUTER
, ".");
56 data
["path"] = "/".into();
57 // hack: add invisible space to sort as first entry
58 data
["text"] = "​Management API (HTTP)".into();
59 data
["expanded"] = true.into();
63 let mut data
= dump_api_schema(&api2
::backup
::BACKUP_API_ROUTER
, "/backup/_upgrade_");
64 data
["path"] = "/backup/_upgrade_".into();
65 data
["text"] = "Backup API (HTTP/2)".into();
68 let mut data
= dump_api_schema(&api2
::reader
::READER_API_ROUTER
, "/reader/_upgrade_");
69 data
["path"] = "/reader/_upgrade_".into();
70 data
["text"] = "Restore API (HTTP/2)".into();
73 format
!("var apiSchema = {};", serde_json
::to_string_pretty(&tree
).unwrap())
76 pub fn dump_schema(schema
: &Schema
) -> Value
{
86 Schema
::Boolean(boolean_schema
) => {
89 "description": boolean_schema
.description
,
91 if let Some(default) = boolean_schema
.default {
92 data
["default"] = default.into();
95 Schema
::String(string_schema
) => {
98 "description": string_schema
.description
,
100 if let Some(default) = string_schema
.default {
101 data
["default"] = default.into();
103 if let Some(min_length
) = string_schema
.min_length
{
104 data
["minLength"] = min_length
.into();
106 if let Some(max_length
) = string_schema
.max_length
{
107 data
["maxLength"] = max_length
.into();
109 if let Some(type_text
) = string_schema
.type_text
{
110 data
["typetext"] = type_text
.into();
112 match string_schema
.format
{
113 None
| Some(ApiStringFormat
::VerifyFn(_
)) => { /* do nothing */ }
114 Some(ApiStringFormat
::Pattern(const_regex
)) => {
115 data
["pattern"] = format
!("/{}/", const_regex
.regex_string
)
118 Some(ApiStringFormat
::Enum(variants
)) => {
119 let variants
: Vec
<String
> = variants
121 .map(|e
| e
.value
.to_string())
123 data
["enum"] = serde_json
::to_value(variants
).unwrap();
125 Some(ApiStringFormat
::PropertyString(subschema
)) => {
128 Schema
::Object(_
) | Schema
::Array(_
) => {
129 data
["format"] = dump_schema(subschema
);
130 data
["typetext"] = get_property_string_type_text(subschema
)
133 _
=> { /* do nothing - shouldnot happen */ }
137 // fixme: dump format
139 Schema
::Integer(integer_schema
) => {
142 "description": integer_schema
.description
,
144 if let Some(default) = integer_schema
.default {
145 data
["default"] = default.into();
147 if let Some(minimum
) = integer_schema
.minimum
{
148 data
["minimum"] = minimum
.into();
150 if let Some(maximum
) = integer_schema
.maximum
{
151 data
["maximum"] = maximum
.into();
154 Schema
::Number(number_schema
) => {
157 "description": number_schema
.description
,
159 if let Some(default) = number_schema
.default {
160 data
["default"] = default.into();
162 if let Some(minimum
) = number_schema
.minimum
{
163 data
["minimum"] = minimum
.into();
165 if let Some(maximum
) = number_schema
.maximum
{
166 data
["maximum"] = maximum
.into();
169 Schema
::Object(object_schema
) => {
170 data
= dump_property_schema(object_schema
);
171 data
["type"] = "object".into();
172 if let Some(default_key
) = object_schema
.default_key
{
173 data
["default_key"] = default_key
.into();
176 Schema
::Array(array_schema
) => {
179 "description": array_schema
.description
,
180 "items": dump_schema(array_schema
.items
),
182 if let Some(min_length
) = array_schema
.min_length
{
183 data
["minLength"] = min_length
.into();
185 if let Some(max_length
) = array_schema
.min_length
{
186 data
["maxLength"] = max_length
.into();
189 Schema
::AllOf(alloff_schema
) => {
190 data
= dump_property_schema(alloff_schema
);
191 data
["type"] = "object".into();
198 pub fn dump_property_schema(param
: &dyn ObjectSchemaType
) -> Value
{
199 let mut properties
= json
!({}
);
201 for (prop
, optional
, schema
) in param
.properties() {
202 let mut property
= dump_schema(schema
);
204 property
["optional"] = 1.into
();
206 properties
[prop
] = property
;
210 "description": param
.description(),
211 "additionalProperties": param
.additional_properties(),
212 "properties": properties
,
218 fn dump_api_permission(permission
: &Permission
) -> Value
{
221 Permission
::Superuser
=> json
!({ "user": "root@pam" }
),
222 Permission
::User(user
) => json
!({ "user": user }
),
223 Permission
::Anybody
=> json
!({ "user": "all" }
),
224 Permission
::World
=> json
!({ "user": "world" }
),
225 Permission
::UserParam(param
) => json
!({ "userParam": param }
),
226 Permission
::Group(group
) => json
!({ "group": group }
),
227 Permission
::WithParam(param
, sub_permission
) => {
231 "permissions": dump_api_permission(sub_permission
),
235 Permission
::Privilege(name
, value
, partial
) => {
237 let mut privs
= Vec
::new();
238 for (name
, v
) in PRIVILEGES
{
239 if (value
& v
) != 0 {
240 privs
.push(name
.to_string());
252 Permission
::And(list
) => {
253 let list
: Vec
<Value
> = list
.iter().map(|p
| dump_api_permission(p
)).collect();
254 json
!({ "and": list }
)
256 Permission
::Or(list
) => {
257 let list
: Vec
<Value
> = list
.iter().map(|p
| dump_api_permission(p
)).collect();
258 json
!({ "or": list }
)
263 fn dump_api_method_schema(
265 api_method
: &ApiMethod
,
267 let mut data
= json
!({
268 "description": api_method
.parameters
.description(),
271 data
["parameters"] = dump_property_schema(&api_method
.parameters
);
273 let mut returns
= dump_schema(api_method
.returns
.schema
);
274 if api_method
.returns
.optional
{
275 returns
["optional"] = 1.into
();
277 data
["returns"] = returns
;
279 match api_method
.access
{
280 ApiAccess { description: None, permission: Permission::Superuser }
=> {
281 // no need to output default
283 ApiAccess { description, permission }
=> {
284 let mut permissions
= dump_api_permission(permission
);
285 if let Some(description
) = description
{
286 permissions
["description"] = description
.into();
288 data
["permissions"] = permissions
;
292 let mut method
= method
;
294 if let ApiHandler
::AsyncHttp(_
) = api_method
.handler
{
295 method
= if method
== "POST" { "UPLOAD" }
else { method }
;
296 method
= if method
== "GET" { "DOWNLOAD" }
else { method }
;
299 data
["method"] = method
.into();
304 pub fn dump_api_schema(
309 let mut data
= json
!({}
);
311 let mut info
= json
!({}
);
312 if let Some(api_method
) = router
.get
{
313 info
["GET"] = dump_api_method_schema("GET", api_method
);
315 if let Some(api_method
) = router
.post
{
316 info
["POST"] = dump_api_method_schema("POST", api_method
);
318 if let Some(api_method
) = router
.put
{
319 info
["PUT"] = dump_api_method_schema("PUT", api_method
);
321 if let Some(api_method
) = router
.delete
{
322 info
["DELETE"] = dump_api_method_schema("DELETE", api_method
);
327 match &router
.subroute
{
329 data
["leaf"] = 1.into
();
331 Some(SubRoute
::MatchAll { router, param_name }
) => {
332 let sub_path
= if path
== "." {
333 format
!("/{{{}}}", param_name
)
335 format
!("{}/{{{}}}", path
, param_name
)
337 let mut child
= dump_api_schema(router
, &sub_path
);
338 child
["path"] = sub_path
.into();
339 child
["text"] = format
!("{{{}}}", param_name
).into();
341 let mut children
= Vec
::new();
342 children
.push(child
);
343 data
["children"] = children
.into();
344 data
["leaf"] = 0.into
();
346 Some(SubRoute
::Map(dirmap
)) => {
348 let mut children
= Vec
::new();
350 for (key
, sub_router
) in dirmap
.iter() {
351 let sub_path
= if path
== "." {
354 format
!("{}/{}", path
, key
)
356 let mut child
= dump_api_schema(sub_router
, &sub_path
);
357 child
["path"] = sub_path
.into();
358 child
["text"] = key
.to_string().into();
359 children
.push(child
);
362 data
["children"] = children
.into();
363 data
["leaf"] = 0.into
();