ferrishot_knus/
decode.rs

1//! Decode support stuff
2//!
3//! Mostly useful for manual implementation of various `Decode*` traits.
4use std::any::{Any, TypeId};
5use std::collections::HashMap;
6use std::default::Default;
7use std::fmt;
8
9use crate::ast::{BuiltinType, Literal, SpannedNode, Value};
10use crate::errors::{DecodeError, ExpectedType};
11use crate::traits::{Decode, ErrorSpan};
12
13/// Context is passed through all the decode operations and can be used for:
14///
15/// 1. To emit error and proceed (so multiple errors presented to user)
16/// 2. To store and retrieve data in decoders of nodes, scalars and spans
17#[derive(Debug, Default)]
18pub struct Context<S: ErrorSpan> {
19    errors: Vec<DecodeError<S>>,
20    extensions: HashMap<TypeId, Box<dyn Any>>,
21}
22
23/// Scalar value kind
24///
25/// Currently used only for error reporting
26#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
27pub enum Kind {
28    /// An unquoted integer value, signed or unsigned. Having no decimal point.
29    /// Can be of virtually unlimited length. Can be expressed in binary, octal,
30    /// decimal, or hexadecimal notation.
31    Int,
32    /// A number that has either decimal point or exponential part. Can be only
33    /// in decimal notation. Can represent either decimal or floating value
34    /// value. No quotes.
35    Decimal,
36    /// A string in `"double quotes"` or `r##"raw quotes"##`
37    String,
38    /// A boolean value of `true` or `false`
39    Bool,
40    /// The null value (usually corresponds to `None` in Rust)
41    #[default]
42    Null,
43    /// Not a number
44    Nan,
45    /// Infinity
46    Inf,
47    /// Negative infinity
48    NegInf,
49}
50
51/// Decodes KDL value as bytes
52///
53/// Used internally by `#[ferrishot_knus(..., bytes)]` attribute. But can be used
54/// manually for implementing [`DecodeScalar`](crate::traits::DecodeScalar).
55pub fn bytes<S: ErrorSpan>(value: &Value<S>, ctx: &mut Context<S>) -> Vec<u8> {
56    if let Some(typ) = &value.type_name {
57        match typ.as_builtin() {
58            Some(&BuiltinType::Base64) => {
59                #[cfg(feature = "base64")]
60                {
61                    use base64::{engine::general_purpose::STANDARD, Engine};
62                    match &*value.literal {
63                        Literal::String(s) => match STANDARD.decode(s.as_bytes()) {
64                            Ok(vec) => vec,
65                            Err(e) => {
66                                ctx.emit_error(DecodeError::conversion(&value.literal, e));
67                                Default::default()
68                            }
69                        },
70                        _ => {
71                            ctx.emit_error(DecodeError::scalar_kind(Kind::String, &value.literal));
72                            Default::default()
73                        }
74                    }
75                }
76                #[cfg(not(feature = "base64"))]
77                {
78                    ctx.emit_error(DecodeError::unsupported(
79                        &value.literal,
80                        "base64 support is not compiled in",
81                    ));
82                    Default::default()
83                }
84            }
85            _ => {
86                ctx.emit_error(DecodeError::TypeName {
87                    span: typ.span().clone(),
88                    found: Some(typ.value.clone()),
89                    expected: ExpectedType::optional(BuiltinType::Base64),
90                    rust_type: "bytes",
91                });
92                Default::default()
93            }
94        }
95    } else {
96        match &*value.literal {
97            Literal::String(s) => s.as_bytes().to_vec(),
98            _ => {
99                ctx.emit_error(DecodeError::scalar_kind(Kind::String, &value.literal));
100                Default::default()
101            }
102        }
103    }
104}
105
106/// Emits error(s) if node is not a flag node
107///
108/// Flag node is a node that has no arguments, properties or children.
109///
110/// Used internally by `#[ferrishot_knus(child)] x: bool,`. But can be used
111/// manually for implementing [`DecodeScalar`](crate::traits::DecodeScalar).
112pub fn check_flag_node<S: ErrorSpan>(node: &SpannedNode<S>, ctx: &mut Context<S>) {
113    for arg in &node.arguments {
114        ctx.emit_error(DecodeError::unexpected(
115            &arg.literal,
116            "argument",
117            "unexpected argument",
118        ));
119    }
120    for name in node.properties.keys() {
121        ctx.emit_error(DecodeError::unexpected(
122            name,
123            "property",
124            format!("unexpected property `{}`", name.escape_default()),
125        ));
126    }
127    if let Some(children) = &node.children {
128        for child in children.iter() {
129            ctx.emit_error(DecodeError::unexpected(
130                child,
131                "node",
132                format!("unexpected node `{}`", child.node_name.escape_default()),
133            ));
134        }
135    }
136}
137
138/// Parse single KDL node from AST
139pub fn node<T, S>(ast: &SpannedNode<S>) -> Result<T, Vec<DecodeError<S>>>
140where
141    T: Decode<S>,
142    S: ErrorSpan,
143{
144    let mut ctx = Context::new();
145    match Decode::decode_node(ast, &mut ctx) {
146        Ok(_) if ctx.has_errors() => Err(ctx.into_errors()),
147        Err(e) => {
148            ctx.emit_error(e);
149            Err(ctx.into_errors())
150        }
151        Ok(v) => Ok(v),
152    }
153}
154
155impl<S: ErrorSpan> Context<S> {
156    pub(crate) fn new() -> Context<S> {
157        Context {
158            errors: Vec::new(),
159            extensions: HashMap::new(),
160        }
161    }
162    /// Add error
163    ///
164    /// This fails decoding operation similarly to just returning error value.
165    /// But unlike result allows returning some dummy value and allows decoder
166    /// to proceed so multiple errors are presented to user at the same time.
167    pub fn emit_error(&mut self, err: impl Into<DecodeError<S>>) {
168        self.errors.push(err.into());
169    }
170    /// Returns `true` if any errors was emitted into the context
171    pub fn has_errors(&self) -> bool {
172        !self.errors.is_empty()
173    }
174    pub(crate) fn into_errors(self) -> Vec<DecodeError<S>> {
175        self.errors
176    }
177    /// Set context value
178    ///
179    /// These values aren't used by the ferrishot_knus itself. But can be used by
180    /// user-defined decoders to get some value. Each type can have a single but
181    /// separate value set. So users are encouraged to use [new type idiom
182    /// ](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)
183    /// to avoid conflicts with other libraries.
184    ///
185    /// It's also discourated to use `set` in the decoder. It's expeced that
186    /// context will be filled in using
187    /// [`parse_with_context`](crate::parse_with_context) function.
188    pub fn set<T: 'static>(&mut self, value: T) {
189        self.extensions.insert(TypeId::of::<T>(), Box::new(value));
190    }
191    /// Get context value
192    ///
193    /// Returns a value previously set in context
194    pub fn get<T: 'static>(&self) -> Option<&T> {
195        self.extensions
196            .get(&TypeId::of::<T>())
197            .and_then(|b| b.downcast_ref())
198    }
199}
200
201impl fmt::Display for Kind {
202    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203        f.write_str(self.as_str())
204    }
205}
206
207impl From<&'_ Literal> for Kind {
208    fn from(lit: &Literal) -> Kind {
209        use Kind as K;
210        use Literal as L;
211        match lit {
212            L::Int(_) => K::Int,
213            L::Decimal(_) => K::Decimal,
214            L::String(_) => K::String,
215            L::Bool(_) => K::Bool,
216            L::Null => K::Null,
217            L::Nan => K::Nan,
218            L::Inf => K::Inf,
219            L::NegInf => K::NegInf,
220        }
221    }
222}
223
224impl Kind {
225    /// Returns the string representation of `Kind`
226    ///
227    /// This is currently used in error messages.
228    pub const fn as_str(&self) -> &'static str {
229        use Kind::*;
230        match self {
231            Int => "integer",
232            Decimal => "decimal",
233            String => "string",
234            Bool => "boolean",
235            Null => "null",
236            Nan => "nan",
237            Inf => "inf",
238            NegInf => "-inf",
239        }
240    }
241}