]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/docgen.rs
bump proxmox dependency to 0.14.0 and proxmox-http to 0.5.0
[proxmox-backup.git] / src / bin / docgen.rs
CommitLineData
2322a980 1use anyhow::{bail, Error};
fee0fe54 2use serde_json::{json, Value};
2322a980 3
fee0fe54
DM
4use proxmox::{
5 api::{
bc235831 6 schema::{
a37c8d24 7 ApiType,
bc235831
DM
8 Schema,
9 ObjectSchemaType,
8616a4af 10 ApiStringFormat,
bc235831 11 },
2037d9af
DM
12 router::{
13 ApiAccess,
14 },
fee0fe54
DM
15 format::{
16 dump_enum_properties,
17 dump_section_config,
8616a4af 18 get_property_string_type_text,
fee0fe54
DM
19 },
20 ApiMethod,
21 ApiHandler,
22 Router,
23 SubRoute,
2037d9af 24 Permission,
fee0fe54 25 },
2ca396c0 26};
2322a980 27
8cc3760e
DM
28use pbs_api_types::PRIVILEGES;
29
e7d4be9d 30use proxmox_backup::api2;
2322a980 31
2322a980
DM
32fn get_args() -> (String, Vec<String>) {
33
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();
38
39 (prefix, args)
40}
41
42fn main() -> Result<(), Error> {
43
44 let (_prefix, args) = get_args();
45
46 if args.len() < 1 {
47 bail!("missing arguments");
48 }
fee0fe54 49
2322a980 50 for arg in args.iter() {
2ca396c0 51 let text = match arg.as_ref() {
fee0fe54 52 "apidata.js" => generate_api_tree(),
e7d4be9d 53 "datastore.cfg" => dump_section_config(&pbs_config::datastore::CONFIG),
1ce8e905 54 "tape.cfg" => dump_section_config(&pbs_config::drive::CONFIG),
e3619d41 55 "tape-job.cfg" => dump_section_config(&pbs_config::tape_job::CONFIG),
ba3d7e19 56 "user.cfg" => dump_section_config(&pbs_config::user::CONFIG),
6afdda88 57 "remote.cfg" => dump_section_config(&pbs_config::remote::CONFIG),
a4e5a0fc 58 "sync.cfg" => dump_section_config(&pbs_config::sync::CONFIG),
802189f7 59 "verification.cfg" => dump_section_config(&pbs_config::verify::CONFIG),
aad2d162 60 "media-pool.cfg" => dump_section_config(&pbs_config::media_pool::CONFIG),
8cc3760e 61 "config::acl::Role" => dump_enum_properties(&pbs_api_types::Role::API_SCHEMA)?,
2322a980 62 _ => bail!("docgen: got unknown type"),
2ca396c0
DM
63 };
64 println!("{}", text);
2322a980 65 }
fee0fe54 66
2322a980
DM
67 Ok(())
68}
fee0fe54
DM
69
70fn generate_api_tree() -> String {
71
fee0fe54 72 let mut tree = Vec::new();
0bf4b813
DM
73
74 let mut data = dump_api_schema(& api2::ROUTER, ".");
fee0fe54 75 data["path"] = "/".into();
0bf4b813
DM
76 // hack: add invisible space to sort as first entry
77 data["text"] = "&#x200b;Management API (HTTP)".into();
fee0fe54
DM
78 data["expanded"] = true.into();
79
80 tree.push(data);
81
451856d2
DM
82 let mut data = dump_api_schema(&api2::backup::BACKUP_API_ROUTER, "/backup/_upgrade_");
83 data["path"] = "/backup/_upgrade_".into();
0bf4b813
DM
84 data["text"] = "Backup API (HTTP/2)".into();
85 tree.push(data);
86
451856d2
DM
87 let mut data = dump_api_schema(&api2::reader::READER_API_ROUTER, "/reader/_upgrade_");
88 data["path"] = "/reader/_upgrade_".into();
0bf4b813
DM
89 data["text"] = "Restore API (HTTP/2)".into();
90 tree.push(data);
91
85417b2a 92 format!("var apiSchema = {};", serde_json::to_string_pretty(&tree).unwrap())
fee0fe54
DM
93}
94
bc235831
DM
95pub fn dump_schema(schema: &Schema) -> Value {
96
97 let mut data;
98
99 match schema {
100 Schema::Null => {
101 data = json!({
102 "type": "null",
103 });
104 }
105 Schema::Boolean(boolean_schema) => {
106 data = json!({
107 "type": "boolean",
108 "description": boolean_schema.description,
109 });
110 if let Some(default) = boolean_schema.default {
111 data["default"] = default.into();
112 }
113 }
114 Schema::String(string_schema) => {
115 data = json!({
116 "type": "string",
117 "description": string_schema.description,
118 });
119 if let Some(default) = string_schema.default {
120 data["default"] = default.into();
121 }
122 if let Some(min_length) = string_schema.min_length {
123 data["minLength"] = min_length.into();
124 }
125 if let Some(max_length) = string_schema.max_length {
126 data["maxLength"] = max_length.into();
127 }
128 if let Some(type_text) = string_schema.type_text {
129 data["typetext"] = type_text.into();
130 }
8616a4af
DM
131 match string_schema.format {
132 None | Some(ApiStringFormat::VerifyFn(_)) => { /* do nothing */ }
133 Some(ApiStringFormat::Pattern(const_regex)) => {
aa30663c
DM
134 data["pattern"] = format!("/{}/", const_regex.regex_string)
135 .into();
8616a4af
DM
136 }
137 Some(ApiStringFormat::Enum(variants)) => {
138 let variants: Vec<String> = variants
139 .iter()
140 .map(|e| e.value.to_string())
141 .collect();
142 data["enum"] = serde_json::to_value(variants).unwrap();
143 }
144 Some(ApiStringFormat::PropertyString(subschema)) => {
145
146 match subschema {
147 Schema::Object(_) | Schema::Array(_) => {
148 data["format"] = dump_schema(subschema);
149 data["typetext"] = get_property_string_type_text(subschema)
150 .into();
151 }
152 _ => { /* do nothing - shouldnot happen */ }
153 };
154 }
155 }
bc235831
DM
156 // fixme: dump format
157 }
158 Schema::Integer(integer_schema) => {
159 data = json!({
160 "type": "integer",
161 "description": integer_schema.description,
162 });
163 if let Some(default) = integer_schema.default {
164 data["default"] = default.into();
165 }
166 if let Some(minimum) = integer_schema.minimum {
167 data["minimum"] = minimum.into();
168 }
169 if let Some(maximum) = integer_schema.maximum {
170 data["maximum"] = maximum.into();
171 }
172 }
173 Schema::Number(number_schema) => {
174 data = json!({
175 "type": "number",
176 "description": number_schema.description,
177 });
178 if let Some(default) = number_schema.default {
179 data["default"] = default.into();
180 }
181 if let Some(minimum) = number_schema.minimum {
182 data["minimum"] = minimum.into();
183 }
184 if let Some(maximum) = number_schema.maximum {
185 data["maximum"] = maximum.into();
186 }
187 }
188 Schema::Object(object_schema) => {
189 data = dump_property_schema(object_schema);
190 data["type"] = "object".into();
8616a4af
DM
191 if let Some(default_key) = object_schema.default_key {
192 data["default_key"] = default_key.into();
193 }
bc235831
DM
194 }
195 Schema::Array(array_schema) => {
196 data = json!({
197 "type": "array",
198 "description": array_schema.description,
199 "items": dump_schema(array_schema.items),
200 });
201 if let Some(min_length) = array_schema.min_length {
202 data["minLength"] = min_length.into();
203 }
204 if let Some(max_length) = array_schema.min_length {
205 data["maxLength"] = max_length.into();
206 }
207 }
208 Schema::AllOf(alloff_schema) => {
209 data = dump_property_schema(alloff_schema);
210 data["type"] = "object".into();
211 }
212 };
213
214 data
215}
216
3554fe64 217pub fn dump_property_schema(param: &dyn ObjectSchemaType) -> Value {
bc235831
DM
218 let mut properties = json!({});
219
220 for (prop, optional, schema) in param.properties() {
221 let mut property = dump_schema(schema);
222 if *optional {
223 property["optional"] = 1.into();
224 }
225 properties[prop] = property;
226 }
227
228 let data = json!({
229 "description": param.description(),
230 "additionalProperties": param.additional_properties(),
231 "properties": properties,
232 });
233
234 data
235}
236
2037d9af
DM
237fn dump_api_permission(permission: &Permission) -> Value {
238
239 match permission {
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) => {
247 json!({
248 "withParam": {
249 "name": param,
250 "permissions": dump_api_permission(sub_permission),
251 },
252 })
253 }
254 Permission::Privilege(name, value, partial) => {
255
256 let mut privs = Vec::new();
257 for (name, v) in PRIVILEGES {
258 if (value & v) != 0 {
259 privs.push(name.to_string());
260 }
261 }
262
263 json!({
264 "check": {
265 "path": name,
266 "privs": privs,
267 "partial": partial,
268 }
269 })
270 }
271 Permission::And(list) => {
272 let list: Vec<Value> = list.iter().map(|p| dump_api_permission(p)).collect();
273 json!({ "and": list })
274 }
275 Permission::Or(list) => {
276 let list: Vec<Value> = list.iter().map(|p| dump_api_permission(p)).collect();
277 json!({ "or": list })
278 }
279 }
280}
281
fee0fe54
DM
282fn dump_api_method_schema(
283 method: &str,
284 api_method: &ApiMethod,
285) -> Value {
286 let mut data = json!({
287 "description": api_method.parameters.description(),
288 });
289
bc235831 290 data["parameters"] = dump_property_schema(&api_method.parameters);
fee0fe54 291
bc235831
DM
292 let mut returns = dump_schema(&api_method.returns.schema);
293 if api_method.returns.optional {
294 returns["optional"] = 1.into();
295 }
296 data["returns"] = returns;
fee0fe54 297
2037d9af
DM
298 match api_method.access {
299 ApiAccess { description: None, permission: Permission::Superuser } => {
300 // no need to output default
301 }
302 ApiAccess { description, permission } => {
303 let mut permissions = dump_api_permission(permission);
304 if let Some(description) = description {
305 permissions["description"] = description.into();
306 }
307 data["permissions"] = permissions;
308 }
309 }
310
fee0fe54
DM
311 let mut method = method;
312
313 if let ApiHandler::AsyncHttp(_) = api_method.handler {
314 method = if method == "POST" { "UPLOAD" } else { method };
315 method = if method == "GET" { "DOWNLOAD" } else { method };
316 }
317
318 data["method"] = method.into();
319
320 data
321}
322
323pub fn dump_api_schema(
324 router: &Router,
325 path: &str,
326) -> Value {
327
328 let mut data = json!({});
329
330 let mut info = json!({});
331 if let Some(api_method) = router.get {
332 info["GET"] = dump_api_method_schema("GET", api_method);
333 }
334 if let Some(api_method) = router.post {
335 info["POST"] = dump_api_method_schema("POST", api_method);
336 }
337 if let Some(api_method) = router.put {
338 info["PUT"] = dump_api_method_schema("PUT", api_method);
339 }
340 if let Some(api_method) = router.delete {
341 info["DELETE"] = dump_api_method_schema("DELETE", api_method);
342 }
343
344 data["info"] = info;
345
346 match &router.subroute {
347 None => {
348 data["leaf"] = 1.into();
349 },
350 Some(SubRoute::MatchAll { router, param_name }) => {
351 let sub_path = if path == "." {
352 format!("/{{{}}}", param_name)
353 } else {
354 format!("{}/{{{}}}", path, param_name)
355 };
356 let mut child = dump_api_schema(router, &sub_path);
357 child["path"] = sub_path.into();
358 child["text"] = format!("{{{}}}", param_name).into();
359
360 let mut children = Vec::new();
361 children.push(child);
362 data["children"] = children.into();
363 data["leaf"] = 0.into();
364 }
365 Some(SubRoute::Map(dirmap)) => {
366
367 let mut children = Vec::new();
368
369 for (key, sub_router) in dirmap.iter() {
370 let sub_path = if path == "." {
371 format!("/{}", key)
372 } else {
373 format!("{}/{}", path, key)
374 };
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);
379 }
380
381 data["children"] = children.into();
382 data["leaf"] = 0.into();
383 }
384 }
385
386 data
387}