iocaine 3.4.0

The deadliest poison known to AI
// SPDX-FileCopyrightText: Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: MIT

#![allow(clippy::result_large_err)]

use figment::{
    Error,
    value::{Empty, Value},
};
use kdl::{KdlEntry, KdlNode, KdlValue};

pub trait KdlEntryExt {
    type Error;

    fn to_figment_value(&self) -> Value;
    fn as_string(&self) -> Result<String, Self::Error>;
}

impl KdlEntryExt for KdlEntry {
    type Error = Error;

    #[allow(clippy::cast_possible_truncation)]
    fn to_figment_value(&self) -> Value {
        match self.value() {
            KdlValue::String(s) => Value::from(s.to_owned()),
            KdlValue::Integer(i) => Value::from(*i as i64),
            KdlValue::Float(f) => Value::from(*f),
            KdlValue::Bool(b) => Value::from(*b),
            KdlValue::Null => Empty::Unit.into(),
        }
    }

    fn as_string(&self) -> Result<String, Self::Error> {
        if let KdlValue::String(s) = self.value() {
            Ok(s.to_owned())
        } else {
            Err(Error::from(format!(
                "not a string value at {}",
                self.span().offset()
            )))
        }
    }
}

pub trait KdlNodeExt {
    type Error;

    fn assert_single_argument(&self) -> Result<&Self, Self::Error>;
    fn assert_max_arguments(&self, n: usize) -> Result<&Self, Self::Error>;
    fn assert_no_children(&self) -> Result<&Self, Self::Error>;

    fn get_first_string_argument(&self) -> Result<&str, Self::Error>;
    fn get_first_integer_argument(&self) -> Result<i128, Self::Error>;
    fn get_string_property(&self, key: &str) -> Result<Option<&str>, Error>;
    fn get_integer_property(&self, key: &str) -> Result<Option<i128>, Error>;
    fn get_bool(&self) -> Result<bool, Error>;
}

impl KdlNodeExt for KdlNode {
    type Error = Error;

    fn get_bool(&self) -> Result<bool, Error> {
        let Some(value) = self.entry(0) else {
            return Ok(true);
        };

        let Some(value) = value.value().as_bool() else {
            return Err(Error::from(format!(
                r#""{}" at {} requires a bool argument"#,
                self.name(),
                self.span().offset(),
            )));
        };

        Ok(value)
    }

    fn get_string_property(&self, key: &str) -> Result<Option<&str>, Error> {
        match self.get(key) {
            Some(v) => {
                let Some(v) = v.as_string() else {
                    return Err(Error::from(format!(
                        r#""{key}" at {} requires a string argument"#,
                        self.span().offset(),
                    )));
                };
                Ok(Some(v))
            }
            None => Ok(None),
        }
    }

    fn get_integer_property(&self, key: &str) -> Result<Option<i128>, Error> {
        match self.get(key) {
            Some(v) => {
                let Some(v) = v.as_integer() else {
                    return Err(Error::from(format!(
                        r#""{key}" at {} requires a numeric argument"#,
                        self.span().offset(),
                    )));
                };
                Ok(Some(v))
            }
            None => Ok(None),
        }
    }

    fn assert_max_arguments(&self, n: usize) -> Result<&Self, Self::Error> {
        if self.len() > n {
            return Err(Error::from(format!(
                r#""{}" at {} requires at most {n} arguments, found {}"#,
                self.name().value(),
                self.span().offset(),
                self.len()
            )));
        }
        Ok(self)
    }

    fn assert_single_argument(&self) -> Result<&Self, Self::Error> {
        if self.len() != 1 {
            return Err(Error::from(format!(
                r#""{}" at {} requires exactly one argument, found {}"#,
                self.name().value(),
                self.span().offset(),
                self.len()
            )));
        }
        Ok(self)
    }

    fn get_first_string_argument(&self) -> Result<&str, Self::Error> {
        self.entry(0)
            .and_then(|e| e.value().as_string())
            .ok_or_else(|| {
                Error::from(format!(
                    r#""{}" at {} requires a string argument"#,
                    self.name().value(),
                    self.span().offset()
                ))
            })
    }

    fn get_first_integer_argument(&self) -> Result<i128, Self::Error> {
        self.entry(0)
            .and_then(|e| e.value().as_integer())
            .ok_or_else(|| {
                Error::from(format!(
                    r#""{}" at {} requires a numeric argument"#,
                    self.name().value(),
                    self.span().offset()
                ))
            })
    }

    fn assert_no_children(&self) -> Result<&Self, Self::Error> {
        if self.children().is_some() {
            return Err(Error::from(format!(
                r#""{}" at {} should have no children"#,
                self.name().value(),
                self.span().offset()
            )));
        }
        Ok(self)
    }
}