]>
git.proxmox.com Git - proxmox.git/blob - proxmox-schema/src/format.rs
4e497ba5b32d56d80cb81e4003a19d6c23b3b163
1 //! Module to generate and format API Documenation
3 use anyhow
::{bail, Error}
;
7 /// Enumerate different styles to display parameters/properties.
8 #[derive(Copy, Clone, PartialEq, Eq)]
9 pub enum ParameterDisplayStyle
{
10 /// Used for properties in configuration files: ``key:``
12 /// Used for PropertyStings properties in configuration files
14 /// Used for command line options: ``--key``
16 /// Used for command line options passed as arguments: ``<key>``
20 /// CLI usage information format.
21 #[derive(Copy, Clone, PartialEq, Eq)]
22 pub enum DocumentationFormat
{
23 /// Text, command line only (one line).
25 /// Text, list all options.
27 /// Text, include description.
29 /// Like full, but in reStructuredText format.
33 /// Line wrapping to form simple list of paragraphs.
36 subsequent_indent
: &str,
40 let wrap_options1
= textwrap
::Options
::new(columns
)
41 .initial_indent(initial_indent
)
42 .subsequent_indent(subsequent_indent
);
44 let wrap_options2
= textwrap
::Options
::new(columns
)
45 .initial_indent(subsequent_indent
)
46 .subsequent_indent(subsequent_indent
);
50 .filter(|p
| !p
.is_empty())
51 .fold(String
::new(), |mut acc
, p
| {
53 acc
.push_str(&textwrap
::wrap(p
, &wrap_options1
).join("\n"));
55 acc
.push_str(&textwrap
::wrap(p
, &wrap_options2
).join("\n"));
64 let text
= "Command. This may be a list in order to spefify nested sub-commands.";
65 let expect
= " Command. This may be a list in order to spefify nested sub-\n commands.\n\n";
68 let wrapped
= wrap_text(indent
, indent
, text
, 80);
70 assert_eq
!(wrapped
, expect
);
73 fn get_simple_type_text(schema
: &Schema
, list_enums
: bool
) -> String
{
75 Schema
::Null
=> String
::from("<null>"), // should not happen
76 Schema
::Boolean(_
) => String
::from("<1|0>"),
77 Schema
::Integer(_
) => String
::from("<integer>"),
78 Schema
::Number(_
) => String
::from("<number>"),
79 Schema
::String(string_schema
) => match string_schema
{
81 type_text
: Some(type_text
),
83 } => String
::from(*type_text
),
85 format
: Some(ApiStringFormat
::Enum(variants
)),
88 if list_enums
&& variants
.len() <= 3 {
89 let list
: Vec
<String
> =
90 variants
.iter().map(|e
| String
::from(e
.value
)).collect();
93 String
::from("<enum>")
96 _
=> String
::from("<string>"),
98 _
=> panic
!("get_simple_type_text: expected simple type"),
102 /// Generate ReST Documentaion for object properties
103 pub fn dump_properties(
104 param
: &dyn ObjectSchemaType
,
106 style
: ParameterDisplayStyle
,
109 let mut res
= String
::new();
110 let next_indent
= format
!(" {}", indent
);
112 let mut required_list
: Vec
<String
> = Vec
::new();
113 let mut optional_list
: Vec
<String
> = Vec
::new();
115 for (prop
, optional
, schema
) in param
.properties() {
116 if skip
.iter().any(|n
| n
== prop
) {
120 let mut param_descr
=
121 get_property_description(prop
, schema
, style
, DocumentationFormat
::ReST
);
123 if !indent
.is_empty() {
124 param_descr
= format
!("{}{}", indent
, param_descr
); // indent first line
125 param_descr
= param_descr
.replace('
\n'
, &format
!("\n{}", indent
)); // indent rest
128 if style
== ParameterDisplayStyle
::Config
{
129 if let Schema
::String(StringSchema
{
130 format
: Some(ApiStringFormat
::PropertyString(sub_schema
)),
135 Schema
::Object(object_schema
) => {
136 let sub_text
= dump_properties(
139 ParameterDisplayStyle
::ConfigSub
,
142 param_descr
.push_str(&sub_text
);
144 Schema
::Array(_
) => {
145 // do nothing - description should explain the list type
152 optional_list
.push(param_descr
);
154 required_list
.push(param_descr
);
158 if !required_list
.is_empty() {
159 if style
!= ParameterDisplayStyle
::ConfigSub
{
160 res
.push_str("\n*Required properties:*\n\n");
163 for text
in required_list
{
169 if !optional_list
.is_empty() {
170 if style
!= ParameterDisplayStyle
::ConfigSub
{
171 res
.push_str("\n*Optional properties:*\n\n");
174 for text
in optional_list
{
183 /// Helper to format an object property, including name, type and description.
184 pub fn get_property_description(
187 style
: ParameterDisplayStyle
,
188 format
: DocumentationFormat
,
190 let type_text
= get_schema_type_text(schema
, style
);
192 let (descr
, default, extra
) = match schema
{
193 Schema
::Null
=> ("null", None
, None
),
194 Schema
::String(ref schema
) => (
196 schema
.default.map(|v
| v
.to_owned()),
199 Schema
::Boolean(ref schema
) => (
201 schema
.default.map(|v
| v
.to_string()),
204 Schema
::Integer(ref schema
) => (
206 schema
.default.map(|v
| v
.to_string()),
209 Schema
::Number(ref schema
) => (
211 schema
.default.map(|v
| v
.to_string()),
214 Schema
::Object(ref schema
) => (schema
.description
, None
, None
),
215 Schema
::AllOf(ref schema
) => (schema
.description
, None
, None
),
216 Schema
::Array(ref schema
) => (
219 Some(String
::from("Can be specified more than once.")),
223 let default_text
= match default {
224 Some(text
) => format
!(" (default={})", text
),
225 None
=> String
::new(),
228 let descr
= match extra
{
229 Some(extra
) => format
!("{} {}", descr
, extra
),
230 None
=> String
::from(descr
),
233 if format
== DocumentationFormat
::ReST
{
234 let mut text
= match style
{
235 ParameterDisplayStyle
::Config
=> {
236 // reST definition list format
237 format
!("``{}`` : ``{}{}``\n ", name
, type_text
, default_text
)
239 ParameterDisplayStyle
::ConfigSub
=> {
240 // reST definition list format
241 format
!("``{}`` = ``{}{}``\n ", name
, type_text
, default_text
)
243 ParameterDisplayStyle
::Arg
=> {
244 // reST option list format
245 format
!("``--{}`` ``{}{}``\n ", name
, type_text
, default_text
)
247 ParameterDisplayStyle
::Fixed
=> {
248 format
!("``<{}>`` : ``{}{}``\n ", name
, type_text
, default_text
)
252 text
.push_str(&wrap_text("", " ", &descr
, 80));
257 let display_name
= match style
{
258 ParameterDisplayStyle
::Config
=> format
!("{}:", name
),
259 ParameterDisplayStyle
::ConfigSub
=> format
!("{}=", name
),
260 ParameterDisplayStyle
::Arg
=> format
!("--{}", name
),
261 ParameterDisplayStyle
::Fixed
=> format
!("<{}>", name
),
264 let mut text
= format
!(" {:-10} {}{}", display_name
, type_text
, default_text
);
267 text
.push_str(&wrap_text(indent
, indent
, &descr
, 80));
273 /// Helper to format the type text
275 /// The result is a short string including important constraints, for
276 /// example ``<integer> (0 - N)``.
277 pub fn get_schema_type_text(schema
: &Schema
, _style
: ParameterDisplayStyle
) -> String
{
279 Schema
::Null
=> String
::from("<null>"), // should not happen
280 Schema
::String(string_schema
) => {
281 match string_schema
{
283 type_text
: Some(type_text
),
285 } => String
::from(*type_text
),
287 format
: Some(ApiStringFormat
::Enum(variants
)),
290 let list
: Vec
<String
> =
291 variants
.iter().map(|e
| String
::from(e
.value
)).collect();
294 // displaying regex add more confision than it helps
295 //StringSchema { format: Some(ApiStringFormat::Pattern(const_regex)), .. } => {
296 // format!("/{}/", const_regex.regex_string)
299 format
: Some(ApiStringFormat
::PropertyString(sub_schema
)),
301 } => get_property_string_type_text(sub_schema
),
302 _
=> String
::from("<string>"),
305 Schema
::Boolean(_
) => String
::from("<boolean>"),
306 Schema
::Integer(integer_schema
) => match (integer_schema
.minimum
, integer_schema
.maximum
) {
307 (Some(min
), Some(max
)) => format
!("<integer> ({} - {})", min
, max
),
308 (Some(min
), None
) => format
!("<integer> ({} - N)", min
),
309 (None
, Some(max
)) => format
!("<integer> (-N - {})", max
),
310 _
=> String
::from("<integer>"),
312 Schema
::Number(number_schema
) => match (number_schema
.minimum
, number_schema
.maximum
) {
313 (Some(min
), Some(max
)) => format
!("<number> ({} - {})", min
, max
),
314 (Some(min
), None
) => format
!("<number> ({} - N)", min
),
315 (None
, Some(max
)) => format
!("<number> (-N - {})", max
),
316 _
=> String
::from("<number>"),
318 Schema
::Object(_
) => String
::from("<object>"),
319 Schema
::Array(schema
) => get_schema_type_text(schema
.items
, _style
),
320 Schema
::AllOf(_
) => String
::from("<object>"),
324 pub fn get_property_string_type_text(schema
: &Schema
) -> String
{
326 Schema
::Object(object_schema
) => get_object_type_text(object_schema
),
327 Schema
::Array(array_schema
) => {
328 let item_type
= get_simple_type_text(array_schema
.items
, true);
329 format
!("[{}, ...]", item_type
)
331 _
=> panic
!("get_property_string_type_text: expected array or object"),
335 fn get_object_type_text(object_schema
: &ObjectSchema
) -> String
{
336 let mut parts
= Vec
::new();
338 let mut add_part
= |name
, optional
, schema
| {
339 let tt
= get_simple_type_text(schema
, false);
340 let text
= if parts
.is_empty() {
341 format
!("{}={}", name
, tt
)
343 format
!(",{}={}", name
, tt
)
346 parts
.push(format
!("[{}]", text
));
352 // add default key first
353 if let Some(ref default_key
) = object_schema
.default_key
{
354 let (optional
, schema
) = object_schema
.lookup(default_key
).unwrap();
355 add_part(default_key
, optional
, schema
);
359 for (name
, optional
, schema
) in object_schema
.properties
{
363 if let Some(ref default_key
) = object_schema
.default_key
{
364 if name
== default_key
{
368 add_part(name
, *optional
, schema
);
372 for (name
, optional
, schema
) in object_schema
.properties
{
376 if let Some(ref default_key
) = object_schema
.default_key
{
377 if name
== default_key
{
381 add_part(name
, *optional
, schema
);
384 let mut type_text
= String
::new();
386 type_text
.push_str(&parts
.join(" "));
391 /// Generate ReST Documentaion for enumeration.
392 pub fn dump_enum_properties(schema
: &Schema
) -> Result
<String
, Error
> {
393 let mut res
= String
::new();
395 if let Schema
::String(StringSchema
{
396 format
: Some(ApiStringFormat
::Enum(variants
)),
400 for item
in variants
.iter() {
403 let _
= write
!(res
, ":``{}``: ", item
.value
);
404 let descr
= wrap_text("", " ", item
.description
, 80);
405 res
.push_str(&descr
);
411 bail
!("dump_enum_properties failed - not an enum");
414 pub fn dump_api_return_schema(returns
: &ReturnType
, style
: ParameterDisplayStyle
) -> String
{
417 let schema
= &returns
.schema
;
419 let mut res
= if returns
.optional
{
420 "*Returns* (optionally): ".to_string()
422 "*Returns*: ".to_string()
425 let type_text
= get_schema_type_text(schema
, style
);
426 let _
= write
!(res
, "**{}**\n\n", type_text
);
432 Schema
::Boolean(schema
) => {
433 let description
= wrap_text("", "", schema
.description
, 80);
434 res
.push_str(&description
);
436 Schema
::Integer(schema
) => {
437 let description
= wrap_text("", "", schema
.description
, 80);
438 res
.push_str(&description
);
440 Schema
::Number(schema
) => {
441 let description
= wrap_text("", "", schema
.description
, 80);
442 res
.push_str(&description
);
444 Schema
::String(schema
) => {
445 let description
= wrap_text("", "", schema
.description
, 80);
446 res
.push_str(&description
);
448 Schema
::Array(schema
) => {
449 let description
= wrap_text("", "", schema
.description
, 80);
450 res
.push_str(&description
);
452 Schema
::Object(obj_schema
) => {
453 let description
= wrap_text("", "", obj_schema
.description
, 80);
454 res
.push_str(&description
);
455 res
.push_str(&dump_properties(obj_schema
, "", style
, &[]));
457 Schema
::AllOf(all_of_schema
) => {
458 let description
= wrap_text("", "", all_of_schema
.description
, 80);
459 res
.push_str(&description
);
460 res
.push_str(&dump_properties(all_of_schema
, "", style
, &[]));