mlt-core 0.20.1

MapLibre Tile library code
Documentation
use std::any::type_name;
use std::{fmt, mem};

use crate::{Decoder, MltError, MltResult};

pub trait Decode<Parsed>: Sized {
    fn decode(self, decoder: &mut Decoder) -> MltResult<Parsed>;
}

mod sealed {
    pub trait Sealed {}
}

/// Type-state marker for [`Layer01`](crate::Layer01) and related column wrappers.
///
/// Implementors determine how `(Raw, Parsed)` column pairs are stored:
/// - [`Lazy`] stores an [`LazyParsed<Raw, Parsed>`] enum that can be in `Raw`, `Parsed`, or `ParsingFailed` state.
/// - [`Parsed`] stores only `Parsed`, giving zero-cost infallible field access.
pub trait DecodeState: sealed::Sealed {
    type LazyOrParsed<Raw: fmt::Debug + Clone, Parsed: fmt::Debug + Clone>: fmt::Debug + Clone;
}

/// Lazy state: individual columns may still be raw or already decoded.
///
/// This is the default state returned by [`Parser::parse_layers`](crate::Parser::parse_layers).
/// All columns can be decoded at once by calling [`Layer01::decode_all`](crate::Layer01::decode_all),
/// which consumes `self` and returns a [`Layer01<Parsed>`](crate::Layer01).
#[derive(Debug, Clone, PartialEq)]
pub struct Lazy;

/// Fully-decoded state: all columns hold their parsed values directly.
///
/// A `Layer01<Parsed>` is produced by [`Layer01::decode_all`](crate::Layer01::decode_all).
/// Its fields (`id`, `geometry`, `properties`) are the parsed types themselves - no
/// wrapping enum, no `Result`, just plain field access.
#[derive(Debug, Clone, PartialEq)]
pub struct Parsed;

impl sealed::Sealed for Lazy {}
impl sealed::Sealed for Parsed {}

impl DecodeState for Lazy {
    type LazyOrParsed<Raw: fmt::Debug + Clone, Parsed: fmt::Debug + Clone> =
        LazyParsed<Raw, Parsed>;
}
impl DecodeState for Parsed {
    /// In the decoded state the column IS the parsed value - no enum wrapper.
    type LazyOrParsed<Raw: fmt::Debug + Clone, Parsed: fmt::Debug + Clone> = Parsed;
}

/// Shared wrapper for values that may still be in the original (raw) format or
/// already parsed (but still columnar).
/// Used by: `Id`, `Geometry`, `Property`, and eventually - `SharedDictItem`
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub enum LazyParsed<Raw, Parsed> {
    Raw(Raw),
    Parsed(Parsed),
    ParsingFailed,
}

impl<Raw: Decode<Parsed>, Parsed> LazyParsed<Raw, Parsed> {
    /// Decode in place, replacing the raw value with the parsed result.
    pub fn decode(&mut self, decoder: &mut Decoder) -> MltResult<&mut Parsed> {
        match self {
            Self::Parsed(v) => Ok(v),
            Self::Raw(_) => {
                let Self::Raw(raw) = mem::replace(self, Self::ParsingFailed) else {
                    unreachable!();
                };
                *self = Self::Parsed(raw.decode(decoder)?);
                let Self::Parsed(v) = self else {
                    unreachable!()
                };
                Ok(v)
            }
            Self::ParsingFailed => Err(MltError::PriorParseFailure),
        }
    }

    /// Consume and return the parsed value, decoding if currently raw.
    pub fn into_parsed(self, decoder: &mut Decoder) -> MltResult<Parsed> {
        match self {
            Self::Parsed(v) => Ok(v),
            Self::Raw(raw) => raw.decode(decoder),
            Self::ParsingFailed => Err(MltError::PriorParseFailure),
        }
    }

    pub fn as_parsed(&self) -> MltResult<&Parsed> {
        match self {
            Self::Parsed(v) => Ok(v),
            Self::Raw(_) => Err(MltError::NotDecoded(type_name::<Parsed>())),
            Self::ParsingFailed => Err(MltError::PriorParseFailure),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct RawWord(&'static str);

    impl Decode<String> for RawWord {
        fn decode(self, _decoder: &mut Decoder) -> MltResult<String> {
            Ok(self.0.to_owned())
        }
    }

    #[test]
    fn as_parsed_on_raw_names_the_parsed_type() {
        let lazy = LazyParsed::<RawWord, String>::Raw(RawWord("x"));
        assert_eq!(
            lazy.as_parsed().unwrap_err().to_string(),
            "alloc::string::String is not decoded"
        );
    }

    #[test]
    fn as_parsed_on_parsed_returns_the_value() {
        let lazy = LazyParsed::<RawWord, String>::Parsed("x".to_owned());
        assert_eq!(lazy.as_parsed().unwrap(), "x");
    }

    #[test]
    fn as_parsed_after_failure_reports_prior_failure() {
        let lazy = LazyParsed::<RawWord, String>::ParsingFailed;
        assert_eq!(
            lazy.as_parsed().unwrap_err().to_string(),
            "previous decode/parsing attempt failed"
        );
    }
}