]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/docgen.rs
docgen: improve api schema dump
[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
DM
6 schema::{
7 Schema,
8 ObjectSchemaType,
9 SchemaPropertyEntry,
10 },
fee0fe54
DM
11 format::{
12 dump_enum_properties,
13 dump_section_config,
14 },
15 ApiMethod,
16 ApiHandler,
17 Router,
18 SubRoute,
19 },
2ca396c0 20};
2322a980
DM
21
22use proxmox_backup::{
fee0fe54 23 api2,
2322a980
DM
24 config,
25};
26
2322a980
DM
27fn get_args() -> (String, Vec<String>) {
28
29 let mut args = std::env::args();
30 let prefix = args.next().unwrap();
31 let prefix = prefix.rsplit('/').next().unwrap().to_string(); // without path
32 let args: Vec<String> = args.collect();
33
34 (prefix, args)
35}
36
37fn main() -> Result<(), Error> {
38
39 let (_prefix, args) = get_args();
40
41 if args.len() < 1 {
42 bail!("missing arguments");
43 }
fee0fe54 44
2322a980 45 for arg in args.iter() {
2ca396c0 46 let text = match arg.as_ref() {
fee0fe54 47 "apidata.js" => generate_api_tree(),
2ca396c0
DM
48 "datastore.cfg" => dump_section_config(&config::datastore::CONFIG),
49 "tape.cfg" => dump_section_config(&config::drive::CONFIG),
7ca0ba45 50 "tape-job.cfg" => dump_section_config(&config::tape_job::CONFIG),
2ca396c0
DM
51 "user.cfg" => dump_section_config(&config::user::CONFIG),
52 "remote.cfg" => dump_section_config(&config::remote::CONFIG),
53 "sync.cfg" => dump_section_config(&config::sync::CONFIG),
5b7f4455 54 "verification.cfg" => dump_section_config(&config::verify::CONFIG),
2ca396c0
DM
55 "media-pool.cfg" => dump_section_config(&config::media_pool::CONFIG),
56 "config::acl::Role" => dump_enum_properties(&config::acl::Role::API_SCHEMA)?,
2322a980 57 _ => bail!("docgen: got unknown type"),
2ca396c0
DM
58 };
59 println!("{}", text);
2322a980 60 }
fee0fe54 61
2322a980
DM
62 Ok(())
63}
fee0fe54
DM
64
65fn generate_api_tree() -> String {
66
fee0fe54 67 let mut tree = Vec::new();
0bf4b813
DM
68
69 let mut data = dump_api_schema(& api2::ROUTER, ".");
fee0fe54 70 data["path"] = "/".into();
0bf4b813
DM
71 // hack: add invisible space to sort as first entry
72 data["text"] = "&#x200b;Management API (HTTP)".into();
fee0fe54
DM
73 data["expanded"] = true.into();
74
75 tree.push(data);
76
0bf4b813
DM
77 let mut data = dump_api_schema(&api2::backup::BACKUP_API_ROUTER, ".");
78 data["path"] = "/".into();
79 data["text"] = "Backup API (HTTP/2)".into();
80 tree.push(data);
81
82 let mut data = dump_api_schema(&api2::reader::READER_API_ROUTER, ".");
83 data["path"] = "/".into();
84 data["text"] = "Restore API (HTTP/2)".into();
85 tree.push(data);
86
fee0fe54
DM
87 format!("var pbsapi = {};", serde_json::to_string_pretty(&tree).unwrap())
88}
89
bc235831
DM
90pub fn dump_schema(schema: &Schema) -> Value {
91
92 let mut data;
93
94 match schema {
95 Schema::Null => {
96 data = json!({
97 "type": "null",
98 });
99 }
100 Schema::Boolean(boolean_schema) => {
101 data = json!({
102 "type": "boolean",
103 "description": boolean_schema.description,
104 });
105 if let Some(default) = boolean_schema.default {
106 data["default"] = default.into();
107 }
108 }
109 Schema::String(string_schema) => {
110 data = json!({
111 "type": "string",
112 "description": string_schema.description,
113 });
114 if let Some(default) = string_schema.default {
115 data["default"] = default.into();
116 }
117 if let Some(min_length) = string_schema.min_length {
118 data["minLength"] = min_length.into();
119 }
120 if let Some(max_length) = string_schema.max_length {
121 data["maxLength"] = max_length.into();
122 }
123 if let Some(type_text) = string_schema.type_text {
124 data["typetext"] = type_text.into();
125 }
126 // fixme: dump format
127 }
128 Schema::Integer(integer_schema) => {
129 data = json!({
130 "type": "integer",
131 "description": integer_schema.description,
132 });
133 if let Some(default) = integer_schema.default {
134 data["default"] = default.into();
135 }
136 if let Some(minimum) = integer_schema.minimum {
137 data["minimum"] = minimum.into();
138 }
139 if let Some(maximum) = integer_schema.maximum {
140 data["maximum"] = maximum.into();
141 }
142 }
143 Schema::Number(number_schema) => {
144 data = json!({
145 "type": "number",
146 "description": number_schema.description,
147 });
148 if let Some(default) = number_schema.default {
149 data["default"] = default.into();
150 }
151 if let Some(minimum) = number_schema.minimum {
152 data["minimum"] = minimum.into();
153 }
154 if let Some(maximum) = number_schema.maximum {
155 data["maximum"] = maximum.into();
156 }
157 }
158 Schema::Object(object_schema) => {
159 data = dump_property_schema(object_schema);
160 data["type"] = "object".into();
161 }
162 Schema::Array(array_schema) => {
163 data = json!({
164 "type": "array",
165 "description": array_schema.description,
166 "items": dump_schema(array_schema.items),
167 });
168 if let Some(min_length) = array_schema.min_length {
169 data["minLength"] = min_length.into();
170 }
171 if let Some(max_length) = array_schema.min_length {
172 data["maxLength"] = max_length.into();
173 }
174 }
175 Schema::AllOf(alloff_schema) => {
176 data = dump_property_schema(alloff_schema);
177 data["type"] = "object".into();
178 }
179 };
180
181 data
182}
183
184pub fn dump_property_schema<I>(
185 param: &dyn ObjectSchemaType<PropertyIter = I>,
186) -> Value
187 where I: Iterator<Item = &'static SchemaPropertyEntry>,
188{
189 let mut properties = json!({});
190
191 for (prop, optional, schema) in param.properties() {
192 let mut property = dump_schema(schema);
193 if *optional {
194 property["optional"] = 1.into();
195 }
196 properties[prop] = property;
197 }
198
199 let data = json!({
200 "description": param.description(),
201 "additionalProperties": param.additional_properties(),
202 "properties": properties,
203 });
204
205 data
206}
207
fee0fe54
DM
208fn dump_api_method_schema(
209 method: &str,
210 api_method: &ApiMethod,
211) -> Value {
212 let mut data = json!({
213 "description": api_method.parameters.description(),
214 });
215
bc235831 216 data["parameters"] = dump_property_schema(&api_method.parameters);
fee0fe54 217
bc235831
DM
218 let mut returns = dump_schema(&api_method.returns.schema);
219 if api_method.returns.optional {
220 returns["optional"] = 1.into();
221 }
222 data["returns"] = returns;
fee0fe54
DM
223
224 let mut method = method;
225
226 if let ApiHandler::AsyncHttp(_) = api_method.handler {
227 method = if method == "POST" { "UPLOAD" } else { method };
228 method = if method == "GET" { "DOWNLOAD" } else { method };
229 }
230
231 data["method"] = method.into();
232
233 data
234}
235
236pub fn dump_api_schema(
237 router: &Router,
238 path: &str,
239) -> Value {
240
241 let mut data = json!({});
242
243 let mut info = json!({});
244 if let Some(api_method) = router.get {
245 info["GET"] = dump_api_method_schema("GET", api_method);
246 }
247 if let Some(api_method) = router.post {
248 info["POST"] = dump_api_method_schema("POST", api_method);
249 }
250 if let Some(api_method) = router.put {
251 info["PUT"] = dump_api_method_schema("PUT", api_method);
252 }
253 if let Some(api_method) = router.delete {
254 info["DELETE"] = dump_api_method_schema("DELETE", api_method);
255 }
256
257 data["info"] = info;
258
259 match &router.subroute {
260 None => {
261 data["leaf"] = 1.into();
262 },
263 Some(SubRoute::MatchAll { router, param_name }) => {
264 let sub_path = if path == "." {
265 format!("/{{{}}}", param_name)
266 } else {
267 format!("{}/{{{}}}", path, param_name)
268 };
269 let mut child = dump_api_schema(router, &sub_path);
270 child["path"] = sub_path.into();
271 child["text"] = format!("{{{}}}", param_name).into();
272
273 let mut children = Vec::new();
274 children.push(child);
275 data["children"] = children.into();
276 data["leaf"] = 0.into();
277 }
278 Some(SubRoute::Map(dirmap)) => {
279
280 let mut children = Vec::new();
281
282 for (key, sub_router) in dirmap.iter() {
283 let sub_path = if path == "." {
284 format!("/{}", key)
285 } else {
286 format!("{}/{}", path, key)
287 };
288 let mut child = dump_api_schema(sub_router, &sub_path);
289 child["path"] = sub_path.into();
290 child["text"] = key.to_string().into();
291 children.push(child);
292 }
293
294 data["children"] = children.into();
295 data["leaf"] = 0.into();
296 }
297 }
298
299 data
300}