ferrishot_knus 3.3.0

Another KDL language implementation
Documentation
//! Decode support stuff
//!
//! Mostly useful for manual implementation of various `Decode*` traits.
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::default::Default;
use std::fmt;

use crate::ast::{BuiltinType, Literal, SpannedNode, Value};
use crate::errors::{DecodeError, ExpectedType};
use crate::traits::{Decode, ErrorSpan};

/// Context is passed through all the decode operations and can be used for:
///
/// 1. To emit error and proceed (so multiple errors presented to user)
/// 2. To store and retrieve data in decoders of nodes, scalars and spans
#[derive(Debug, Default)]
pub struct Context<S: ErrorSpan> {
    errors: Vec<DecodeError<S>>,
    extensions: HashMap<TypeId, Box<dyn Any>>,
}

/// Scalar value kind
///
/// Currently used only for error reporting
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub enum Kind {
    /// An unquoted integer value, signed or unsigned. Having no decimal point.
    /// Can be of virtually unlimited length. Can be expressed in binary, octal,
    /// decimal, or hexadecimal notation.
    Int,
    /// A number that has either decimal point or exponential part. Can be only
    /// in decimal notation. Can represent either decimal or floating value
    /// value. No quotes.
    Decimal,
    /// A string in `"double quotes"` or `r##"raw quotes"##`
    String,
    /// A boolean value of `true` or `false`
    Bool,
    /// The null value (usually corresponds to `None` in Rust)
    #[default]
    Null,
    /// Not a number
    Nan,
    /// Infinity
    Inf,
    /// Negative infinity
    NegInf,
}

/// Decodes KDL value as bytes
///
/// Used internally by `#[ferrishot_knus(..., bytes)]` attribute. But can be used
/// manually for implementing [`DecodeScalar`](crate::traits::DecodeScalar).
pub fn bytes<S: ErrorSpan>(value: &Value<S>, ctx: &mut Context<S>) -> Vec<u8> {
    if let Some(typ) = &value.type_name {
        match typ.as_builtin() {
            Some(&BuiltinType::Base64) => {
                #[cfg(feature = "base64")]
                {
                    use base64::{engine::general_purpose::STANDARD, Engine};
                    match &*value.literal {
                        Literal::String(s) => match STANDARD.decode(s.as_bytes()) {
                            Ok(vec) => vec,
                            Err(e) => {
                                ctx.emit_error(DecodeError::conversion(&value.literal, e));
                                Default::default()
                            }
                        },
                        _ => {
                            ctx.emit_error(DecodeError::scalar_kind(Kind::String, &value.literal));
                            Default::default()
                        }
                    }
                }
                #[cfg(not(feature = "base64"))]
                {
                    ctx.emit_error(DecodeError::unsupported(
                        &value.literal,
                        "base64 support is not compiled in",
                    ));
                    Default::default()
                }
            }
            _ => {
                ctx.emit_error(DecodeError::TypeName {
                    span: typ.span().clone(),
                    found: Some(typ.value.clone()),
                    expected: ExpectedType::optional(BuiltinType::Base64),
                    rust_type: "bytes",
                });
                Default::default()
            }
        }
    } else {
        match &*value.literal {
            Literal::String(s) => s.as_bytes().to_vec(),
            _ => {
                ctx.emit_error(DecodeError::scalar_kind(Kind::String, &value.literal));
                Default::default()
            }
        }
    }
}

/// Emits error(s) if node is not a flag node
///
/// Flag node is a node that has no arguments, properties or children.
///
/// Used internally by `#[ferrishot_knus(child)] x: bool,`. But can be used
/// manually for implementing [`DecodeScalar`](crate::traits::DecodeScalar).
pub fn check_flag_node<S: ErrorSpan>(node: &SpannedNode<S>, ctx: &mut Context<S>) {
    for arg in &node.arguments {
        ctx.emit_error(DecodeError::unexpected(
            &arg.literal,
            "argument",
            "unexpected argument",
        ));
    }
    for name in node.properties.keys() {
        ctx.emit_error(DecodeError::unexpected(
            name,
            "property",
            format!("unexpected property `{}`", name.escape_default()),
        ));
    }
    if let Some(children) = &node.children {
        for child in children.iter() {
            ctx.emit_error(DecodeError::unexpected(
                child,
                "node",
                format!("unexpected node `{}`", child.node_name.escape_default()),
            ));
        }
    }
}

/// Parse single KDL node from AST
pub fn node<T, S>(ast: &SpannedNode<S>) -> Result<T, Vec<DecodeError<S>>>
where
    T: Decode<S>,
    S: ErrorSpan,
{
    let mut ctx = Context::new();
    match Decode::decode_node(ast, &mut ctx) {
        Ok(_) if ctx.has_errors() => Err(ctx.into_errors()),
        Err(e) => {
            ctx.emit_error(e);
            Err(ctx.into_errors())
        }
        Ok(v) => Ok(v),
    }
}

impl<S: ErrorSpan> Context<S> {
    pub(crate) fn new() -> Context<S> {
        Context {
            errors: Vec::new(),
            extensions: HashMap::new(),
        }
    }
    /// Add error
    ///
    /// This fails decoding operation similarly to just returning error value.
    /// But unlike result allows returning some dummy value and allows decoder
    /// to proceed so multiple errors are presented to user at the same time.
    pub fn emit_error(&mut self, err: impl Into<DecodeError<S>>) {
        self.errors.push(err.into());
    }
    /// Returns `true` if any errors was emitted into the context
    pub fn has_errors(&self) -> bool {
        !self.errors.is_empty()
    }
    pub(crate) fn into_errors(self) -> Vec<DecodeError<S>> {
        self.errors
    }
    /// Set context value
    ///
    /// These values aren't used by the ferrishot_knus itself. But can be used by
    /// user-defined decoders to get some value. Each type can have a single but
    /// separate value set. So users are encouraged to use [new type idiom
    /// ](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)
    /// to avoid conflicts with other libraries.
    ///
    /// It's also discourated to use `set` in the decoder. It's expeced that
    /// context will be filled in using
    /// [`parse_with_context`](crate::parse_with_context) function.
    pub fn set<T: 'static>(&mut self, value: T) {
        self.extensions.insert(TypeId::of::<T>(), Box::new(value));
    }
    /// Get context value
    ///
    /// Returns a value previously set in context
    pub fn get<T: 'static>(&self) -> Option<&T> {
        self.extensions
            .get(&TypeId::of::<T>())
            .and_then(|b| b.downcast_ref())
    }
}

impl fmt::Display for Kind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl From<&'_ Literal> for Kind {
    fn from(lit: &Literal) -> Kind {
        use Kind as K;
        use Literal as L;
        match lit {
            L::Int(_) => K::Int,
            L::Decimal(_) => K::Decimal,
            L::String(_) => K::String,
            L::Bool(_) => K::Bool,
            L::Null => K::Null,
            L::Nan => K::Nan,
            L::Inf => K::Inf,
            L::NegInf => K::NegInf,
        }
    }
}

impl Kind {
    /// Returns the string representation of `Kind`
    ///
    /// This is currently used in error messages.
    pub const fn as_str(&self) -> &'static str {
        use Kind::*;
        match self {
            Int => "integer",
            Decimal => "decimal",
            String => "string",
            Bool => "boolean",
            Null => "null",
            Nan => "nan",
            Inf => "inf",
            NegInf => "-inf",
        }
    }
}