1 use anyhow
::{bail, Error}
;
2 use serde_json
::{json, Value}
;
18 get_property_string_type_text
,
36 fn get_args() -> (String
, Vec
<String
>) {
38 let mut args
= std
::env
::args();
39 let prefix
= args
.next().unwrap();
40 let prefix
= prefix
.rsplit('
/'
).next().unwrap().to_string(); // without path
41 let args
: Vec
<String
> = args
.collect();
46 fn main() -> Result
<(), Error
> {
48 let (_prefix
, args
) = get_args();
51 bail
!("missing arguments");
54 for arg
in args
.iter() {
55 let text
= match arg
.as_ref() {
56 "apidata.js" => generate_api_tree(),
57 "datastore.cfg" => dump_section_config(&config
::datastore
::CONFIG
),
58 "tape.cfg" => dump_section_config(&pbs_config
::drive
::CONFIG
),
59 "tape-job.cfg" => dump_section_config(&pbs_config
::tape_job
::CONFIG
),
60 "user.cfg" => dump_section_config(&config
::user
::CONFIG
),
61 "remote.cfg" => dump_section_config(&pbs_config
::remote
::CONFIG
),
62 "sync.cfg" => dump_section_config(&pbs_config
::sync
::CONFIG
),
63 "verification.cfg" => dump_section_config(&pbs_config
::verify
::CONFIG
),
64 "media-pool.cfg" => dump_section_config(&pbs_config
::media_pool
::CONFIG
),
65 "config::acl::Role" => dump_enum_properties(&config
::acl
::Role
::API_SCHEMA
)?
,
66 _
=> bail
!("docgen: got unknown type"),
74 fn generate_api_tree() -> String
{
76 let mut tree
= Vec
::new();
78 let mut data
= dump_api_schema(& api2
::ROUTER
, ".");
79 data
["path"] = "/".into();
80 // hack: add invisible space to sort as first entry
81 data
["text"] = "​Management API (HTTP)".into();
82 data
["expanded"] = true.into();
86 let mut data
= dump_api_schema(&api2
::backup
::BACKUP_API_ROUTER
, "/backup/_upgrade_");
87 data
["path"] = "/backup/_upgrade_".into();
88 data
["text"] = "Backup API (HTTP/2)".into();
91 let mut data
= dump_api_schema(&api2
::reader
::READER_API_ROUTER
, "/reader/_upgrade_");
92 data
["path"] = "/reader/_upgrade_".into();
93 data
["text"] = "Restore API (HTTP/2)".into();
96 format
!("var apiSchema = {};", serde_json
::to_string_pretty(&tree
).unwrap())
99 pub fn dump_schema(schema
: &Schema
) -> Value
{
109 Schema
::Boolean(boolean_schema
) => {
112 "description": boolean_schema
.description
,
114 if let Some(default) = boolean_schema
.default {
115 data
["default"] = default.into();
118 Schema
::String(string_schema
) => {
121 "description": string_schema
.description
,
123 if let Some(default) = string_schema
.default {
124 data
["default"] = default.into();
126 if let Some(min_length
) = string_schema
.min_length
{
127 data
["minLength"] = min_length
.into();
129 if let Some(max_length
) = string_schema
.max_length
{
130 data
["maxLength"] = max_length
.into();
132 if let Some(type_text
) = string_schema
.type_text
{
133 data
["typetext"] = type_text
.into();
135 match string_schema
.format
{
136 None
| Some(ApiStringFormat
::VerifyFn(_
)) => { /* do nothing */ }
137 Some(ApiStringFormat
::Pattern(const_regex
)) => {
138 data
["pattern"] = format
!("/{}/", const_regex
.regex_string
)
141 Some(ApiStringFormat
::Enum(variants
)) => {
142 let variants
: Vec
<String
> = variants
144 .map(|e
| e
.value
.to_string())
146 data
["enum"] = serde_json
::to_value(variants
).unwrap();
148 Some(ApiStringFormat
::PropertyString(subschema
)) => {
151 Schema
::Object(_
) | Schema
::Array(_
) => {
152 data
["format"] = dump_schema(subschema
);
153 data
["typetext"] = get_property_string_type_text(subschema
)
156 _
=> { /* do nothing - shouldnot happen */ }
160 // fixme: dump format
162 Schema
::Integer(integer_schema
) => {
165 "description": integer_schema
.description
,
167 if let Some(default) = integer_schema
.default {
168 data
["default"] = default.into();
170 if let Some(minimum
) = integer_schema
.minimum
{
171 data
["minimum"] = minimum
.into();
173 if let Some(maximum
) = integer_schema
.maximum
{
174 data
["maximum"] = maximum
.into();
177 Schema
::Number(number_schema
) => {
180 "description": number_schema
.description
,
182 if let Some(default) = number_schema
.default {
183 data
["default"] = default.into();
185 if let Some(minimum
) = number_schema
.minimum
{
186 data
["minimum"] = minimum
.into();
188 if let Some(maximum
) = number_schema
.maximum
{
189 data
["maximum"] = maximum
.into();
192 Schema
::Object(object_schema
) => {
193 data
= dump_property_schema(object_schema
);
194 data
["type"] = "object".into();
195 if let Some(default_key
) = object_schema
.default_key
{
196 data
["default_key"] = default_key
.into();
199 Schema
::Array(array_schema
) => {
202 "description": array_schema
.description
,
203 "items": dump_schema(array_schema
.items
),
205 if let Some(min_length
) = array_schema
.min_length
{
206 data
["minLength"] = min_length
.into();
208 if let Some(max_length
) = array_schema
.min_length
{
209 data
["maxLength"] = max_length
.into();
212 Schema
::AllOf(alloff_schema
) => {
213 data
= dump_property_schema(alloff_schema
);
214 data
["type"] = "object".into();
221 pub fn dump_property_schema(param
: &dyn ObjectSchemaType
) -> Value
{
222 let mut properties
= json
!({}
);
224 for (prop
, optional
, schema
) in param
.properties() {
225 let mut property
= dump_schema(schema
);
227 property
["optional"] = 1.into
();
229 properties
[prop
] = property
;
233 "description": param
.description(),
234 "additionalProperties": param
.additional_properties(),
235 "properties": properties
,
241 fn dump_api_permission(permission
: &Permission
) -> Value
{
244 Permission
::Superuser
=> json
!({ "user": "root@pam" }
),
245 Permission
::User(user
) => json
!({ "user": user }
),
246 Permission
::Anybody
=> json
!({ "user": "all" }
),
247 Permission
::World
=> json
!({ "user": "world" }
),
248 Permission
::UserParam(param
) => json
!({ "userParam": param }
),
249 Permission
::Group(group
) => json
!({ "group": group }
),
250 Permission
::WithParam(param
, sub_permission
) => {
254 "permissions": dump_api_permission(sub_permission
),
258 Permission
::Privilege(name
, value
, partial
) => {
260 let mut privs
= Vec
::new();
261 for (name
, v
) in PRIVILEGES
{
262 if (value
& v
) != 0 {
263 privs
.push(name
.to_string());
275 Permission
::And(list
) => {
276 let list
: Vec
<Value
> = list
.iter().map(|p
| dump_api_permission(p
)).collect();
277 json
!({ "and": list }
)
279 Permission
::Or(list
) => {
280 let list
: Vec
<Value
> = list
.iter().map(|p
| dump_api_permission(p
)).collect();
281 json
!({ "or": list }
)
286 fn dump_api_method_schema(
288 api_method
: &ApiMethod
,
290 let mut data
= json
!({
291 "description": api_method
.parameters
.description(),
294 data
["parameters"] = dump_property_schema(&api_method
.parameters
);
296 let mut returns
= dump_schema(&api_method
.returns
.schema
);
297 if api_method
.returns
.optional
{
298 returns
["optional"] = 1.into
();
300 data
["returns"] = returns
;
302 match api_method
.access
{
303 ApiAccess { description: None, permission: Permission::Superuser }
=> {
304 // no need to output default
306 ApiAccess { description, permission }
=> {
307 let mut permissions
= dump_api_permission(permission
);
308 if let Some(description
) = description
{
309 permissions
["description"] = description
.into();
311 data
["permissions"] = permissions
;
315 let mut method
= method
;
317 if let ApiHandler
::AsyncHttp(_
) = api_method
.handler
{
318 method
= if method
== "POST" { "UPLOAD" }
else { method }
;
319 method
= if method
== "GET" { "DOWNLOAD" }
else { method }
;
322 data
["method"] = method
.into();
327 pub fn dump_api_schema(
332 let mut data
= json
!({}
);
334 let mut info
= json
!({}
);
335 if let Some(api_method
) = router
.get
{
336 info
["GET"] = dump_api_method_schema("GET", api_method
);
338 if let Some(api_method
) = router
.post
{
339 info
["POST"] = dump_api_method_schema("POST", api_method
);
341 if let Some(api_method
) = router
.put
{
342 info
["PUT"] = dump_api_method_schema("PUT", api_method
);
344 if let Some(api_method
) = router
.delete
{
345 info
["DELETE"] = dump_api_method_schema("DELETE", api_method
);
350 match &router
.subroute
{
352 data
["leaf"] = 1.into
();
354 Some(SubRoute
::MatchAll { router, param_name }
) => {
355 let sub_path
= if path
== "." {
356 format
!("/{{{}}}", param_name
)
358 format
!("{}/{{{}}}", path
, param_name
)
360 let mut child
= dump_api_schema(router
, &sub_path
);
361 child
["path"] = sub_path
.into();
362 child
["text"] = format
!("{{{}}}", param_name
).into();
364 let mut children
= Vec
::new();
365 children
.push(child
);
366 data
["children"] = children
.into();
367 data
["leaf"] = 0.into
();
369 Some(SubRoute
::Map(dirmap
)) => {
371 let mut children
= Vec
::new();
373 for (key
, sub_router
) in dirmap
.iter() {
374 let sub_path
= if path
== "." {
377 format
!("{}/{}", path
, key
)
379 let mut child
= dump_api_schema(sub_router
, &sub_path
);
380 child
["path"] = sub_path
.into();
381 child
["text"] = key
.to_string().into();
382 children
.push(child
);
385 data
["children"] = children
.into();
386 data
["leaf"] = 0.into
();