1 use anyhow
::{bail, Error}
;
2 use serde_json
::{json, Value}
;
18 get_property_string_type_text
,
28 use pbs_api_types
::PRIVILEGES
;
30 use proxmox_backup
::{api2, config}
;
32 fn get_args() -> (String
, Vec
<String
>) {
34 let mut args
= std
::env
::args();
35 let prefix
= args
.next().unwrap();
36 let prefix
= prefix
.rsplit('
/'
).next().unwrap().to_string(); // without path
37 let args
: Vec
<String
> = args
.collect();
42 fn main() -> Result
<(), Error
> {
44 let (_prefix
, args
) = get_args();
47 bail
!("missing arguments");
50 for arg
in args
.iter() {
51 let text
= match arg
.as_ref() {
52 "apidata.js" => generate_api_tree(),
53 "datastore.cfg" => dump_section_config(&config
::datastore
::CONFIG
),
54 "tape.cfg" => dump_section_config(&pbs_config
::drive
::CONFIG
),
55 "tape-job.cfg" => dump_section_config(&pbs_config
::tape_job
::CONFIG
),
56 "user.cfg" => dump_section_config(&config
::user
::CONFIG
),
57 "remote.cfg" => dump_section_config(&pbs_config
::remote
::CONFIG
),
58 "sync.cfg" => dump_section_config(&pbs_config
::sync
::CONFIG
),
59 "verification.cfg" => dump_section_config(&pbs_config
::verify
::CONFIG
),
60 "media-pool.cfg" => dump_section_config(&pbs_config
::media_pool
::CONFIG
),
61 "config::acl::Role" => dump_enum_properties(&pbs_api_types
::Role
::API_SCHEMA
)?
,
62 _
=> bail
!("docgen: got unknown type"),
70 fn generate_api_tree() -> String
{
72 let mut tree
= Vec
::new();
74 let mut data
= dump_api_schema(& api2
::ROUTER
, ".");
75 data
["path"] = "/".into();
76 // hack: add invisible space to sort as first entry
77 data
["text"] = "​Management API (HTTP)".into();
78 data
["expanded"] = true.into();
82 let mut data
= dump_api_schema(&api2
::backup
::BACKUP_API_ROUTER
, "/backup/_upgrade_");
83 data
["path"] = "/backup/_upgrade_".into();
84 data
["text"] = "Backup API (HTTP/2)".into();
87 let mut data
= dump_api_schema(&api2
::reader
::READER_API_ROUTER
, "/reader/_upgrade_");
88 data
["path"] = "/reader/_upgrade_".into();
89 data
["text"] = "Restore API (HTTP/2)".into();
92 format
!("var apiSchema = {};", serde_json
::to_string_pretty(&tree
).unwrap())
95 pub fn dump_schema(schema
: &Schema
) -> Value
{
105 Schema
::Boolean(boolean_schema
) => {
108 "description": boolean_schema
.description
,
110 if let Some(default) = boolean_schema
.default {
111 data
["default"] = default.into();
114 Schema
::String(string_schema
) => {
117 "description": string_schema
.description
,
119 if let Some(default) = string_schema
.default {
120 data
["default"] = default.into();
122 if let Some(min_length
) = string_schema
.min_length
{
123 data
["minLength"] = min_length
.into();
125 if let Some(max_length
) = string_schema
.max_length
{
126 data
["maxLength"] = max_length
.into();
128 if let Some(type_text
) = string_schema
.type_text
{
129 data
["typetext"] = type_text
.into();
131 match string_schema
.format
{
132 None
| Some(ApiStringFormat
::VerifyFn(_
)) => { /* do nothing */ }
133 Some(ApiStringFormat
::Pattern(const_regex
)) => {
134 data
["pattern"] = format
!("/{}/", const_regex
.regex_string
)
137 Some(ApiStringFormat
::Enum(variants
)) => {
138 let variants
: Vec
<String
> = variants
140 .map(|e
| e
.value
.to_string())
142 data
["enum"] = serde_json
::to_value(variants
).unwrap();
144 Some(ApiStringFormat
::PropertyString(subschema
)) => {
147 Schema
::Object(_
) | Schema
::Array(_
) => {
148 data
["format"] = dump_schema(subschema
);
149 data
["typetext"] = get_property_string_type_text(subschema
)
152 _
=> { /* do nothing - shouldnot happen */ }
156 // fixme: dump format
158 Schema
::Integer(integer_schema
) => {
161 "description": integer_schema
.description
,
163 if let Some(default) = integer_schema
.default {
164 data
["default"] = default.into();
166 if let Some(minimum
) = integer_schema
.minimum
{
167 data
["minimum"] = minimum
.into();
169 if let Some(maximum
) = integer_schema
.maximum
{
170 data
["maximum"] = maximum
.into();
173 Schema
::Number(number_schema
) => {
176 "description": number_schema
.description
,
178 if let Some(default) = number_schema
.default {
179 data
["default"] = default.into();
181 if let Some(minimum
) = number_schema
.minimum
{
182 data
["minimum"] = minimum
.into();
184 if let Some(maximum
) = number_schema
.maximum
{
185 data
["maximum"] = maximum
.into();
188 Schema
::Object(object_schema
) => {
189 data
= dump_property_schema(object_schema
);
190 data
["type"] = "object".into();
191 if let Some(default_key
) = object_schema
.default_key
{
192 data
["default_key"] = default_key
.into();
195 Schema
::Array(array_schema
) => {
198 "description": array_schema
.description
,
199 "items": dump_schema(array_schema
.items
),
201 if let Some(min_length
) = array_schema
.min_length
{
202 data
["minLength"] = min_length
.into();
204 if let Some(max_length
) = array_schema
.min_length
{
205 data
["maxLength"] = max_length
.into();
208 Schema
::AllOf(alloff_schema
) => {
209 data
= dump_property_schema(alloff_schema
);
210 data
["type"] = "object".into();
217 pub fn dump_property_schema(param
: &dyn ObjectSchemaType
) -> Value
{
218 let mut properties
= json
!({}
);
220 for (prop
, optional
, schema
) in param
.properties() {
221 let mut property
= dump_schema(schema
);
223 property
["optional"] = 1.into
();
225 properties
[prop
] = property
;
229 "description": param
.description(),
230 "additionalProperties": param
.additional_properties(),
231 "properties": properties
,
237 fn dump_api_permission(permission
: &Permission
) -> Value
{
240 Permission
::Superuser
=> json
!({ "user": "root@pam" }
),
241 Permission
::User(user
) => json
!({ "user": user }
),
242 Permission
::Anybody
=> json
!({ "user": "all" }
),
243 Permission
::World
=> json
!({ "user": "world" }
),
244 Permission
::UserParam(param
) => json
!({ "userParam": param }
),
245 Permission
::Group(group
) => json
!({ "group": group }
),
246 Permission
::WithParam(param
, sub_permission
) => {
250 "permissions": dump_api_permission(sub_permission
),
254 Permission
::Privilege(name
, value
, partial
) => {
256 let mut privs
= Vec
::new();
257 for (name
, v
) in PRIVILEGES
{
258 if (value
& v
) != 0 {
259 privs
.push(name
.to_string());
271 Permission
::And(list
) => {
272 let list
: Vec
<Value
> = list
.iter().map(|p
| dump_api_permission(p
)).collect();
273 json
!({ "and": list }
)
275 Permission
::Or(list
) => {
276 let list
: Vec
<Value
> = list
.iter().map(|p
| dump_api_permission(p
)).collect();
277 json
!({ "or": list }
)
282 fn dump_api_method_schema(
284 api_method
: &ApiMethod
,
286 let mut data
= json
!({
287 "description": api_method
.parameters
.description(),
290 data
["parameters"] = dump_property_schema(&api_method
.parameters
);
292 let mut returns
= dump_schema(&api_method
.returns
.schema
);
293 if api_method
.returns
.optional
{
294 returns
["optional"] = 1.into
();
296 data
["returns"] = returns
;
298 match api_method
.access
{
299 ApiAccess { description: None, permission: Permission::Superuser }
=> {
300 // no need to output default
302 ApiAccess { description, permission }
=> {
303 let mut permissions
= dump_api_permission(permission
);
304 if let Some(description
) = description
{
305 permissions
["description"] = description
.into();
307 data
["permissions"] = permissions
;
311 let mut method
= method
;
313 if let ApiHandler
::AsyncHttp(_
) = api_method
.handler
{
314 method
= if method
== "POST" { "UPLOAD" }
else { method }
;
315 method
= if method
== "GET" { "DOWNLOAD" }
else { method }
;
318 data
["method"] = method
.into();
323 pub fn dump_api_schema(
328 let mut data
= json
!({}
);
330 let mut info
= json
!({}
);
331 if let Some(api_method
) = router
.get
{
332 info
["GET"] = dump_api_method_schema("GET", api_method
);
334 if let Some(api_method
) = router
.post
{
335 info
["POST"] = dump_api_method_schema("POST", api_method
);
337 if let Some(api_method
) = router
.put
{
338 info
["PUT"] = dump_api_method_schema("PUT", api_method
);
340 if let Some(api_method
) = router
.delete
{
341 info
["DELETE"] = dump_api_method_schema("DELETE", api_method
);
346 match &router
.subroute
{
348 data
["leaf"] = 1.into
();
350 Some(SubRoute
::MatchAll { router, param_name }
) => {
351 let sub_path
= if path
== "." {
352 format
!("/{{{}}}", param_name
)
354 format
!("{}/{{{}}}", path
, param_name
)
356 let mut child
= dump_api_schema(router
, &sub_path
);
357 child
["path"] = sub_path
.into();
358 child
["text"] = format
!("{{{}}}", param_name
).into();
360 let mut children
= Vec
::new();
361 children
.push(child
);
362 data
["children"] = children
.into();
363 data
["leaf"] = 0.into
();
365 Some(SubRoute
::Map(dirmap
)) => {
367 let mut children
= Vec
::new();
369 for (key
, sub_router
) in dirmap
.iter() {
370 let sub_path
= if path
== "." {
373 format
!("{}/{}", path
, key
)
375 let mut child
= dump_api_schema(sub_router
, &sub_path
);
376 child
["path"] = sub_path
.into();
377 child
["text"] = key
.to_string().into();
378 children
.push(child
);
381 data
["children"] = children
.into();
382 data
["leaf"] = 0.into
();