use figment::{
Error, error,
value::{Dict, Value},
};
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_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());
}
"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_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))
}
}