1 //! Our 'key: value' config format.
5 use anyhow
::{bail, format_err, Error}
;
6 use serde
::{Deserialize, Serialize}
;
10 parse_property_string
, parse_simple_value
, verify_json_object
, ObjectSchemaType
, Schema
,
13 type Object
= serde_json
::Map
<String
, Value
>;
15 fn object_schema(schema
: &'
static Schema
) -> Result
<&'
static dyn ObjectSchemaType
, Error
> {
17 Schema
::Object(schema
) => schema
,
18 Schema
::AllOf(schema
) => schema
,
19 _
=> bail
!("invalid schema for config, must be an object schema"),
23 /// Parse a full string representing a config file.
24 pub fn from_str
<T
: for<'de
> Deserialize
<'de
>>(
26 schema
: &'
static Schema
,
27 ) -> Result
<T
, Error
> {
28 Ok(serde_json
::from_value(value_from_str(input
, schema
)?
)?
)
31 /// Parse a full string representing a config file.
32 pub fn value_from_str(input
: &str, schema
: &'
static Schema
) -> Result
<Value
, Error
> {
33 let schema
= object_schema(schema
)?
;
35 let mut config
= Object
::new();
37 for (lineno
, line
) in input
.lines().enumerate() {
38 let line
= line
.trim();
39 if line
.starts_with('
#') || line.is_empty() {
43 parse_line(&mut config
, line
, schema
)
44 .map_err(|err
| format_err
!("line {}: {}", lineno
, err
))?
;
47 Ok(Value
::Object(config
))
50 /// Parse a single `key: value` line from a config file.
54 schema
: &'
static dyn ObjectSchemaType
,
55 ) -> Result
<(), Error
> {
56 if line
.starts_with('
#') || line.is_empty() {
62 .ok_or_else(|| format_err
!("missing colon to separate key from value"))?
;
64 bail
!("empty key not allowed");
67 let key
= &line
[..colon
];
68 let value
= line
[(colon
+ 1)..].trim_start();
70 parse_key_value(config
, key
, value
, schema
)
73 /// Lookup the key in the schema, parse the value and insert it into the config object.
78 schema
: &'
static dyn ObjectSchemaType
,
79 ) -> Result
<(), Error
> {
80 let schema
= match schema
.lookup(key
) {
81 Some((_optional
, schema
)) => Some(schema
),
82 None
if schema
.additional_properties() => None
,
84 "invalid key '{}' and schema does not allow additional properties",
89 let value
= parse_value(value
, schema
)?
;
90 config
.insert(key
.to_owned(), value
);
94 /// For this we can just reuse the schema's "parse_simple_value".
96 /// "Additional" properties (`None` schema) will simply become strings.
98 /// Note that this does not handle Object or Array types at all, so if we want to support them
99 /// natively without going over a `String` type, we can add this here.
100 fn parse_value(value
: &str, schema
: Option
<&'
static Schema
>) -> Result
<Value
, Error
> {
102 None
=> Ok(Value
::String(value
.to_owned())),
103 Some(schema
) => parse_simple_value(value
, schema
),
107 /// Parse a string as a property string into a deserializable type. This is just a short wrapper
108 /// around deserializing the s
109 pub fn from_property_string
<T
>(input
: &str, schema
: &'
static Schema
) -> Result
<T
, Error
>
111 T
: for<'de
> Deserialize
<'de
>,
113 Ok(serde_json
::from_value(parse_property_string(
118 /// Serialize a data structure using a 'key: value' config file format.
119 pub fn to_bytes
<T
: Serialize
>(value
: &T
, schema
: &'
static Schema
) -> Result
<Vec
<u8>, Error
> {
120 value_to_bytes(&serde_json
::to_value(value
)?
, schema
)
123 /// Serialize a json value using a 'key: value' config file format.
124 pub fn value_to_bytes(value
: &Value
, schema
: &'
static Schema
) -> Result
<Vec
<u8>, Error
> {
125 let schema
= object_schema(schema
)?
;
127 verify_json_object(value
, schema
)?
;
131 .ok_or_else(|| format_err
!("value must be an object"))?
;
133 let mut out
= Vec
::new();
134 object_to_writer(&mut out
, object
)?
;
138 /// Note: the object must have already been verified at this point.
139 fn object_to_writer(output
: &mut dyn Write
, object
: &Object
) -> Result
<(), Error
> {
140 for (key
, value
) in object
.iter() {
142 Value
::Null
=> continue, // delete this entry
143 Value
::Bool(v
) => writeln
!(output
, "{}: {}", key
, v
)?
,
144 Value
::String(v
) => writeln
!(output
, "{}: {}", key
, v
)?
,
145 Value
::Number(v
) => writeln
!(output
, "{}: {}", key
, v
)?
,
146 Value
::Array(_
) => bail
!("arrays are not supported in config files"),
147 Value
::Object(_
) => bail
!("complex objects are not supported in config files"),
155 use proxmox_schema
::ApiType
;
157 // let's just reuse some schema we actually have available:
158 use crate::config
::node
::NodeConfig
;
160 const NODE_CONFIG
: &str = "\
161 acme: account=pebble\n\
162 acmedomain0: test1.invalid.local,plugin=power\n\
163 acmedomain1: test2.invalid.local\n\
166 let data
: NodeConfig
= from_str(NODE_CONFIG
, &NodeConfig
::API_SCHEMA
)
167 .expect("failed to parse simple node config");
169 let config
= to_bytes(&data
, &NodeConfig
::API_SCHEMA
)
170 .expect("failed to serialize node config");
172 assert_eq
!(config
, NODE_CONFIG
.as_bytes());