]>
Commit | Line | Data |
---|---|---|
2322a980 | 1 | use anyhow::{bail, Error}; |
fee0fe54 | 2 | use serde_json::{json, Value}; |
2322a980 | 3 | |
fee0fe54 DM |
4 | use 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 | |
22 | use proxmox_backup::{ | |
fee0fe54 | 23 | api2, |
2322a980 DM |
24 | config, |
25 | }; | |
26 | ||
2322a980 DM |
27 | fn 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 | ||
37 | fn 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 | |
65 | fn 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"] = "​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 |
90 | pub 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 | ||
184 | pub 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 |
208 | fn 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 | ||
236 | pub 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 | } |