#![allow(clippy::result_large_err)]
use figment::{
Error, error,
value::{Dict, Value},
};
use ipnet::IpNet;
use kdl::KdlNode;
use std::cmp::Ordering;
use super::traits::{KdlEntryExt, KdlNodeExt};
pub trait ParseExt {
fn parse_request_server(&self, mode: &str) -> Result<(String, Value), Error>;
fn parse_prometheus_server(&self) -> Result<(String, Value), Error>;
fn parse_declare_handler(&self) -> Result<(&str, Dict), Error>;
fn parse_firewall(&self) -> Result<Dict, Error>;
fn parse_use(&self) -> Result<Dict, Error>;
fn parse_bind(&self) -> Result<(Option<&str>, Option<&str>), Error>;
fn parse_as_figment_value(&self) -> Result<(&str, Value), Error>;
}
impl ParseExt for KdlNode {
fn parse_request_server(&self, mode: &str) -> Result<(String, Value), Error> {
let name = format!(
"{mode}:{}",
self.assert_single_argument()?.get_first_string_argument()?
);
let mut server = Dict::new();
server.insert("mode".into(), mode.into());
for child in self.iter_children() {
match child.name().value() {
"initial-seed" | "initial-seed-file" => {
let arg = child
.assert_no_children()?
.assert_single_argument()?
.get_first_string_argument()?;
server.insert(child.name().value().into(), arg.into());
}
"use" => {
let uses = child.assert_no_children()?.parse_use()?;
server.insert("use".into(), uses.into());
}
"bind" => {
let (bind, socket_access) = child.parse_bind()?;
if let Some(bind) = bind {
server.insert("bind".into(), bind.into());
}
if let Some(socket_access) = socket_access {
server.insert("unix-socket-access".into(), socket_access.into());
}
}
name => {
let kind = error::Kind::Unsupported(error::Actual::Str(name.to_owned()));
return Err(Error::from(kind));
}
}
}
Ok((name, server.into()))
}
fn parse_prometheus_server(&self) -> Result<(String, Value), Error> {
let name = format!(
"prometheus:{}",
self.assert_single_argument()?.get_first_string_argument()?
);
let mut server = Dict::new();
server.insert("mode".into(), "prometheus".into());
for child in self.iter_children() {
match child.name().value() {
"persist-path" | "persist-interval" => {
let arg = child
.assert_no_children()?
.assert_single_argument()?
.get_first_string_argument()?;
server.insert(child.name().value().into(), arg.into());
}
"persist" => {
if let Some(path) = child.get_string_property("path")? {
server.insert("persist-path".into(), path.into());
}
if let Some(interval) = child.get_string_property("interval")? {
server.insert("persist-interval".into(), interval.into());
}
}
"bind" => {
let (bind, socket_access) = child.parse_bind()?;
if let Some(bind) = bind {
server.insert("bind".into(), bind.into());
}
if let Some(socket_access) = socket_access {
server.insert("unix-socket-access".into(), socket_access.into());
}
}
name => {
let kind = error::Kind::Unsupported(error::Actual::Str(name.to_owned()));
return Err(Error::from(kind));
}
}
}
Ok((name, server.into()))
}
fn parse_use(&self) -> Result<Dict, Error> {
let mut uses = Dict::new();
for entry in self.entries() {
let Some(name) = entry.name() else {
return Err(Error::from(format!(
r#""use" at {} cannot have positional arguments"#,
self.span().offset()
)));
};
match name.value() {
"metrics" => {
let node_name = format!("prometheus:{}", entry.as_string()?);
uses.insert("metrics".into(), Value::from(node_name));
}
"handler-from" => {
uses.insert("handler-from".into(), entry.to_figment_value());
}
name => {
let kind = error::Kind::Unsupported(error::Actual::Str(name.to_owned()));
return Err(Error::from(kind));
}
}
}
Ok(uses)
}
fn parse_firewall(&self) -> Result<Dict, Error> {
let mut firewall = Dict::new();
for child in self.iter_children() {
match child.name().value() {
"enable" | "counters" => {
firewall.insert(child.name().value().into(), child.get_bool()?.into());
}
"table" | "timeout" | "gc-interval" => {
let value = child.get_first_string_argument()?;
firewall.insert(child.name().value().into(), value.into());
}
"size" => {
let value = child
.assert_single_argument()?
.get_first_integer_argument()?;
let value = u64::try_from(value).map_err(|_| {
Error::from(format!(
"firewall set size out of bounds at {}",
child.span().offset()
))
})?;
firewall.insert("size".into(), value.into());
}
"priority" => {
let value = child
.assert_single_argument()?
.get_first_integer_argument()?;
let value = i32::try_from(value).map_err(|_| {
Error::from(format!(
"firewall priority out of bounds at {}",
child.span().offset()
))
})?;
firewall.insert("priority".into(), value.into());
}
"allow" => {
let mut allows = Vec::new();
for entry in child.entries() {
if entry.name().is_some() {
return Err(Error::from(format!(
"named properties for firewall.allow are not allowed at {}",
entry.span().offset()
)));
}
let net: IpNet = entry.as_string()?.parse().map_err(|_| {
Error::from(format!(
"unable to parse IP network: `{}` at {}",
entry.value(),
entry.span().offset()
))
})?;
allows.push(net.to_string());
}
firewall.insert("allow".into(), allows.into());
}
"batch" => {
if let Some(batch_size) = child.get_integer_property("size")? {
let value = usize::try_from(batch_size).map_err(|_| {
Error::from(format!(
"firewall batch size out of bounds at {}",
child.span().offset()
))
})?;
firewall.insert("batch-size".into(), value.into());
}
if let Some(flush_interval) = child.get_integer_property("flush-interval")? {
let value = u64::try_from(flush_interval).map_err(|_| {
Error::from(format!(
"firewall batch flush interval out of bounds at {}",
child.span().offset()
))
})?;
firewall.insert("batch-flush-interval".into(), value.into());
}
}
name => {
let kind = error::Kind::Unsupported(error::Actual::Str(name.to_owned()));
return Err(Error::from(kind));
}
}
}
Ok(firewall)
}
fn parse_bind(&self) -> Result<(Option<&str>, Option<&str>), Error> {
let bind = self.get(0).and(Some(self.get_first_string_argument()?));
let socket_access = self
.assert_max_arguments(2)?
.get_string_property("unix-socket-access")?;
Ok((bind, socket_access))
}
fn parse_declare_handler(&self) -> Result<(&str, Dict), Error> {
let mut arg_count = 0;
for entry in self.iter() {
match entry.name() {
None => arg_count += 1,
Some(name) => match name.value() {
"language" | "compiler" | "path" => {}
name => {
let kind = error::Kind::Unsupported(error::Actual::Str(name.to_owned()));
return Err(Error::from(kind));
}
},
}
}
if arg_count != 1 {
return Err(Error::from(format!(
r#""{}" at {} requires exactly one positional argument"#,
self.name().value(),
self.span().offset()
)));
}
let name = self.get_first_string_argument()?;
let mut config = Dict::new();
for child in self.iter_children() {
let (name, value) = child.parse_as_figment_value()?;
config.insert(name.into(), value);
}
let mut handler = Dict::new();
if let Some(language) = self.get_string_property("language")? {
handler.insert("language".into(), language.into());
}
if let Some(path) = self.get_string_property("path")? {
handler.insert("path".into(), path.into());
}
if let Some(compiler) = self.get_string_property("compiler")? {
handler.insert("compiler".into(), compiler.into());
}
if !config.is_empty() {
handler.insert("config".into(), config.into());
}
Ok((name, handler))
}
fn parse_as_figment_value(&self) -> Result<(&str, Value), Error> {
if !self.is_empty() && self.children().is_some() {
return Err(Error::from(format!(
r#""{}" at {} can't have arguments and children"#,
self.name().value(),
self.span().offset(),
)));
}
let name = self.name().value();
if name == "-" {
return Err(Error::from(format!(
r#"node at {} cannot be named "-""#,
self.span().offset(),
)));
}
let value;
match self.len().cmp(&1) {
Ordering::Equal => {
if let Some(entry) = self.entry(0) {
value = entry.to_figment_value();
} else {
return Err(Error::from(format!(
r#""{}" at {} can't have named arguments"#,
self.name().value(),
self.span().offset(),
)));
}
}
Ordering::Greater => {
let mut vals: Vec<Value> = Vec::new();
for entry in self.iter() {
if entry.name().is_none() {
let v = entry.to_figment_value();
vals.push(v);
} else {
return Err(Error::from(format!(
r#""{}" at {} can't have named arguments"#,
self.name().value(),
self.span().offset(),
)));
}
}
value = Value::from(vals);
}
Ordering::Less => {
let mut vals = Dict::new();
for child in self.iter_children() {
let (name, val) = child.parse_as_figment_value()?;
vals.insert(name.into(), val);
}
value = Value::from(vals);
}
}
Ok((name, value))
}
}