]> git.proxmox.com Git - proxmox-backup.git/blob - src/api_schema/format.rs
more clippy fixups
[proxmox-backup.git] / src / api_schema / format.rs
1 use failure::*;
2
3 use std::io::Write;
4 use crate::api_schema::*;
5 use crate::api_schema::router::*;
6
7 #[derive(Copy, Clone)]
8 pub enum ParameterDisplayStyle {
9 Config,
10 //SonfigSub,
11 Arg,
12 Fixed,
13 }
14
15 /// CLI usage information format
16 #[derive(Copy, Clone, PartialEq)]
17 pub enum DocumentationFormat {
18 /// text, command line only (one line)
19 Short,
20 /// text, list all options
21 Long,
22 /// text, include description
23 Full,
24 /// like full, but in reStructuredText format
25 ReST,
26 }
27
28 /// line wrapping to form simple list of paragraphs
29 pub fn wrap_text(initial_indent: &str, subsequent_indent: &str, text: &str, columns: usize) -> String {
30
31 let wrapper1 = textwrap::Wrapper::new(columns)
32 .initial_indent(initial_indent)
33 .subsequent_indent(subsequent_indent);
34
35 let wrapper2 = textwrap::Wrapper::new(columns)
36 .initial_indent(subsequent_indent)
37 .subsequent_indent(subsequent_indent);
38
39 text.split("\n\n")
40 .map(|p| p.trim())
41 .filter(|p| { p.len() != 0 })
42 .fold(String::new(), |mut acc, p| {
43 if acc.len() == 0 {
44 acc.push_str(&wrapper1.wrap(p).concat());
45 } else {
46 acc.push_str(&wrapper2.wrap(p).concat());
47 }
48 acc.push_str("\n\n");
49 acc
50 })
51 }
52
53 pub fn get_schema_type_text(schema: &Schema, _style: ParameterDisplayStyle) -> String {
54 match schema {
55 Schema::Null => String::from("<null>"), // should not happen
56 Schema::String(_) => String::from("<string>"),
57 Schema::Boolean(_) => String::from("<boolean>"),
58 Schema::Integer(integer_schema) => {
59 match (integer_schema.minimum, integer_schema.maximum) {
60 (Some(min), Some(max)) => format!("<integer> ({} - {})", min, max),
61 (Some(min), None) => format!("<integer> ({} - N)", min),
62 (None, Some(max)) => format!("<integer> (-N - {})", max),
63 _ => String::from("<integer>"),
64 }
65 },
66 Schema::Object(_) => String::from("<object>"),
67 Schema::Array(_) => String::from("<array>"),
68 }
69 }
70
71 pub fn get_property_description(
72 name: &str,
73 schema: &Schema,
74 style: ParameterDisplayStyle,
75 format: DocumentationFormat,
76 ) -> String {
77
78 let type_text = get_schema_type_text(schema, style);
79
80 let (descr, default) = match schema {
81 Schema::Null => ("null", None),
82 Schema::String(ref schema) => (schema.description, schema.default.map(|v| v.to_owned())),
83 Schema::Boolean(ref schema) => (schema.description, schema.default.map(|v| v.to_string())),
84 Schema::Integer(ref schema) => (schema.description, schema.default.map(|v| v.to_string())),
85 Schema::Object(ref schema) => (schema.description, None),
86 Schema::Array(ref schema) => (schema.description, None),
87 };
88
89 let default_text = match default {
90 Some(text) => format!(" (default={})", text),
91 None => String::new(),
92 };
93
94 if format == DocumentationFormat::ReST {
95
96 let mut text = match style {
97 ParameterDisplayStyle::Config => {
98 format!(":``{} {}{}``: ", name, type_text, default_text)
99 }
100 ParameterDisplayStyle::Arg => {
101 format!(":``--{} {}{}``: ", name, type_text, default_text)
102 }
103 ParameterDisplayStyle::Fixed => {
104 format!(":``<{}> {}{}``: ", name, type_text, default_text)
105 }
106 };
107
108 text.push_str(&wrap_text("", "", descr, 80));
109 text.push('\n');
110 text.push('\n');
111
112 text
113
114 } else {
115
116 let display_name = match style {
117 ParameterDisplayStyle::Config => {
118 format!("{}:", name)
119 }
120 ParameterDisplayStyle::Arg => {
121 format!("--{}", name)
122 }
123 ParameterDisplayStyle::Fixed => {
124 format!("<{}>", name)
125 }
126 };
127
128 let mut text = format!(" {:-10} {}{}", display_name, type_text, default_text);
129 let indent = " ";
130 text.push('\n');
131 text.push_str(&wrap_text(indent, indent, descr, 80));
132 text.push('\n');
133 text.push('\n');
134
135 text
136 }
137 }
138
139 fn dump_api_parameters(param: &ObjectSchema) -> String {
140
141 let mut res = wrap_text("", "", param.description, 80);
142
143 let properties = &param.properties;
144
145 let mut prop_names: Vec<&str> = properties.keys().map(|v| *v).collect();
146 prop_names.sort();
147
148 let mut required_list: Vec<String> = vec![];
149 let mut optional_list: Vec<String> = vec![];
150
151 for prop in prop_names {
152 let (optional, schema) = properties.get(prop).unwrap();
153
154 let param_descr = get_property_description(
155 prop, &schema, ParameterDisplayStyle::Config, DocumentationFormat::ReST);
156
157 if *optional {
158 optional_list.push(param_descr);
159 } else {
160 required_list.push(param_descr);
161 }
162 }
163
164 if required_list.len() > 0 {
165
166 res.push_str("\n*Required properties:*\n\n");
167
168 for text in required_list {
169 res.push_str(&text);
170 res.push('\n');
171 }
172
173 }
174
175 if optional_list.len() > 0 {
176
177 res.push_str("\n*Optional properties:*\n\n");
178
179 for text in optional_list {
180 res.push_str(&text);
181 res.push('\n');
182 }
183 }
184
185 res
186 }
187
188 fn dump_api_return_schema(schema: &Schema) -> String {
189
190 let mut res = String::from("*Returns*: ");
191
192 let type_text = get_schema_type_text(schema, ParameterDisplayStyle::Config);
193 res.push_str(&format!("**{}**\n\n", type_text));
194
195 match schema {
196 Schema::Null => {
197 return res;
198 }
199 Schema::Boolean(schema) => {
200 let description = wrap_text("", "", schema.description, 80);
201 res.push_str(&description);
202 }
203 Schema::Integer(schema) => {
204 let description = wrap_text("", "", schema.description, 80);
205 res.push_str(&description);
206 }
207 Schema::String(schema) => {
208 let description = wrap_text("", "", schema.description, 80);
209 res.push_str(&description);
210 }
211 Schema::Array(schema) => {
212 let description = wrap_text("", "", schema.description, 80);
213 res.push_str(&description);
214 }
215 Schema::Object(obj_schema) => {
216 res.push_str(&dump_api_parameters(obj_schema));
217
218 }
219 }
220
221 res.push('\n');
222
223 res
224 }
225
226 fn dump_method_definition(method: &str, path: &str, def: &MethodDefinition) -> Option<String> {
227
228 match def {
229 MethodDefinition::None => None,
230 MethodDefinition::Simple(simple_method) => {
231 let param_descr = dump_api_parameters(&simple_method.parameters);
232
233 let return_descr = dump_api_return_schema(&simple_method.returns);
234
235 let res = format!("**{} {}**\n\n{}\n\n{}", method, path, param_descr, return_descr);
236 Some(res)
237 }
238 MethodDefinition::Async(async_method) => {
239 let method = if method == "POST" { "UPLOAD" } else { method };
240 let method = if method == "GET" { "DOWNLOAD" } else { method };
241
242 let param_descr = dump_api_parameters(&async_method.parameters);
243
244 let return_descr = dump_api_return_schema(&async_method.returns);
245
246 let res = format!("**{} {}**\n\n{}\n\n{}", method, path, param_descr, return_descr);
247 Some(res)
248 }
249 }
250 }
251
252 pub fn dump_api(output: &mut dyn Write, router: &Router, path: &str, mut pos: usize) -> Result<(), Error> {
253
254 let mut cond_print = |x| -> Result<_, Error> {
255 if let Some(text) = x {
256 if pos > 0 {
257 writeln!(output, "-----\n")?;
258 }
259 writeln!(output, "{}", text)?;
260 pos += 1;
261 }
262 Ok(())
263 };
264
265 cond_print(dump_method_definition("GET", path, &router.get))?;
266 cond_print(dump_method_definition("POST", path, &router.post))?;
267 cond_print(dump_method_definition("PUT", path, &router.put))?;
268 cond_print(dump_method_definition("DELETE", path, &router.delete))?;
269
270 match &router.subroute {
271 SubRoute::None => return Ok(()),
272 SubRoute::MatchAll { router, param_name } => {
273 let sub_path = if path == "." {
274 format!("<{}>", param_name)
275 } else {
276 format!("{}/<{}>", path, param_name)
277 };
278 dump_api(output, router, &sub_path, pos)?;
279 }
280 SubRoute::Hash(map) => {
281 let mut keys: Vec<&String> = map.keys().collect();
282 keys.sort_unstable_by(|a, b| a.cmp(b));
283 for key in keys {
284 let sub_router = &map[key];
285 let sub_path = if path == "." { key.to_owned() } else { format!("{}/{}", path, key) };
286 dump_api(output, sub_router, &sub_path, pos)?;
287 }
288 }
289 }
290
291 Ok(())
292 }