[pbs-devel] [PATCH v2 backup 12/27] add 'config file format' to tools::config
Wolfgang Bumiller
w.bumiller at proxmox.com
Thu Apr 22 16:01:58 CEST 2021
Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
* Replaces the serde-based parser from v1. Outside API stays similar (with
`from_str`, `from_property_string`, `to_bytes` ...
* Added a very simple testcase.
src/tools.rs | 1 +
src/tools/config.rs | 171 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 172 insertions(+)
create mode 100644 src/tools/config.rs
diff --git a/src/tools.rs b/src/tools.rs
index 890db826..25323881 100644
--- a/src/tools.rs
+++ b/src/tools.rs
@@ -23,6 +23,7 @@ pub mod async_io;
pub mod borrow;
pub mod cert;
pub mod compression;
+pub mod config;
pub mod cpio;
pub mod daemon;
pub mod disks;
diff --git a/src/tools/config.rs b/src/tools/config.rs
new file mode 100644
index 00000000..499bd187
--- /dev/null
+++ b/src/tools/config.rs
@@ -0,0 +1,171 @@
+//! Our 'key: value' config format.
+
+use std::io::Write;
+
+use anyhow::{bail, format_err, Error};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+
+use proxmox::api::schema::{
+ parse_property_string, parse_simple_value, verify_json_object, ObjectSchemaType, Schema,
+};
+
+type Object = serde_json::Map<String, Value>;
+
+fn object_schema(schema: &'static Schema) -> Result<&'static dyn ObjectSchemaType, Error> {
+ Ok(match schema {
+ Schema::Object(schema) => schema,
+ Schema::AllOf(schema) => schema,
+ _ => bail!("invalid schema for config, must be an object schema"),
+ })
+}
+
+/// Parse a full string representing a config file.
+pub fn from_str<T: for<'de> Deserialize<'de>>(
+ input: &str,
+ schema: &'static Schema,
+) -> Result<T, Error> {
+ Ok(serde_json::from_value(value_from_str(input, schema)?)?)
+}
+
+/// Parse a full string representing a config file.
+pub fn value_from_str(input: &str, schema: &'static Schema) -> Result<Value, Error> {
+ let schema = object_schema(schema)?;
+
+ let mut config = Object::new();
+
+ for (lineno, line) in input.lines().enumerate() {
+ let line = line.trim();
+ if line.starts_with('#') || line.is_empty() {
+ continue;
+ }
+
+ parse_line(&mut config, line, schema)
+ .map_err(|err| format_err!("line {}: {}", lineno, err))?;
+ }
+
+ Ok(Value::Object(config))
+}
+
+/// Parse a single `key: value` line from a config file.
+fn parse_line(
+ config: &mut Object,
+ line: &str,
+ schema: &'static dyn ObjectSchemaType,
+) -> Result<(), Error> {
+ if line.starts_with('#') || line.is_empty() {
+ return Ok(());
+ }
+
+ let colon = line
+ .find(':')
+ .ok_or_else(|| format_err!("missing colon to separate key from value"))?;
+ if colon == 0 {
+ bail!("empty key not allowed");
+ }
+
+ let key = &line[..colon];
+ let value = line[(colon + 1)..].trim_start();
+
+ parse_key_value(config, key, value, schema)
+}
+
+/// Lookup the key in the schema, parse the value and insert it into the config object.
+fn parse_key_value(
+ config: &mut Object,
+ key: &str,
+ value: &str,
+ schema: &'static dyn ObjectSchemaType,
+) -> Result<(), Error> {
+ let schema = match schema.lookup(key) {
+ Some((_optional, schema)) => Some(schema),
+ None if schema.additional_properties() => None,
+ None => bail!(
+ "invalid key '{}' and schema does not allow additional properties",
+ key
+ ),
+ };
+
+ let value = parse_value(value, schema)?;
+ config.insert(key.to_owned(), value);
+ Ok(())
+}
+
+/// For this we can just reuse the schema's "parse_simple_value".
+///
+/// "Additional" properties (`None` schema) will simply become strings.
+///
+/// Note that this does not handle Object or Array types at all, so if we want to support them
+/// natively without going over a `String` type, we can add this here.
+fn parse_value(value: &str, schema: Option<&'static Schema>) -> Result<Value, Error> {
+ match schema {
+ None => Ok(Value::String(value.to_owned())),
+ Some(schema) => parse_simple_value(value, schema),
+ }
+}
+
+/// Parse a string as a property string into a deserializable type. This is just a short wrapper
+/// around deserializing the s
+pub fn from_property_string<T>(input: &str, schema: &'static Schema) -> Result<T, Error>
+where
+ T: for<'de> Deserialize<'de>,
+{
+ Ok(serde_json::from_value(parse_property_string(
+ input, schema,
+ )?)?)
+}
+
+/// Serialize a data structure using a 'key: value' config file format.
+pub fn to_bytes<T: Serialize>(value: &T, schema: &'static Schema) -> Result<Vec<u8>, Error> {
+ value_to_bytes(&serde_json::to_value(value)?, schema)
+}
+
+/// Serialize a json value using a 'key: value' config file format.
+pub fn value_to_bytes(value: &Value, schema: &'static Schema) -> Result<Vec<u8>, Error> {
+ let schema = object_schema(schema)?;
+
+ verify_json_object(value, schema)?;
+
+ let object = value
+ .as_object()
+ .ok_or_else(|| format_err!("value must be an object"))?;
+
+ let mut out = Vec::new();
+ object_to_writer(&mut out, object)?;
+ Ok(out)
+}
+
+/// Note: the object must have already been verified at this point.
+fn object_to_writer(output: &mut dyn Write, object: &Object) -> Result<(), Error> {
+ for (key, value) in object.iter() {
+ match value {
+ Value::Null => continue, // delete this entry
+ Value::Bool(v) => writeln!(output, "{}: {}", key, v)?,
+ Value::String(v) => writeln!(output, "{}: {}", key, v)?,
+ Value::Number(v) => writeln!(output, "{}: {}", key, v)?,
+ Value::Array(_) => bail!("arrays are not supported in config files"),
+ Value::Object(_) => bail!("complex objects are not supported in config files"),
+ }
+ }
+ Ok(())
+}
+
+#[test]
+fn test() {
+ // let's just reuse some schema we actually have available:
+ use crate::config::node::NodeConfig;
+
+ const NODE_CONFIG: &str = "\
+ acme: account=pebble\n\
+ acmedomain0: test1.invalid.local,plugin=power\n\
+ acmedomain1: test2.invalid.local\n\
+ ";
+
+ let data: NodeConfig = from_str(NODE_CONFIG, &NodeConfig::API_SCHEMA)
+ .expect("failed to parse simple node config");
+
+ let config = to_bytes(&data, &NodeConfig::API_SCHEMA)
+ .expect("failed to serialize node config");
+
+ assert_eq!(config, NODE_CONFIG.as_bytes());
+}
--
2.20.1
More information about the pbs-devel
mailing list