mlt-core 0.20.1

MapLibre Tile library code
Documentation
//! Column model and parsing specific to tag `0x01` (v1) layers.
//!
//! v2 replaces both types below with `model02::ColumnType02` and
//! `model02::GeoLayout`; the resulting [`super::Layer01`] is shared.

use std::io;
use std::io::Write;

use num_enum::TryFromPrimitive;

use crate::MltError::ParsingColumnType;
use crate::decoder::{Geometry, GeometryValues, Id, ParsedProperty, Property};
use crate::tile::Extent;
use crate::utils::{BinarySerializer as _, parse_string, parse_u8};
use crate::{DecodeState, Lazy, MltRefResult, Parsed, Parser};

/// Bit 0 of the column type byte: the column has a presence stream.
pub(crate) const OPTIONAL_FLAG: u8 = 0b0000_0001;

/// Mask of the column type byte holding the base [`ColumnType`], i.e. everything
/// [`OPTIONAL_FLAG`] does not claim.
pub(crate) const BASE_TYPE_MASK: u8 = 0b1111_1110;

/// Representation of an MLT feature table layer during decoding.
///
/// Holds what every layer version has. A v1 layer is exactly this; a v2 layer is
/// this plus the columns only v2 carries, which `Layer02` adds around it.
///
/// The type parameter `S` controls how columns are stored:
///
/// - `Layer01<'a>` / `Layer01<'a, Lazy>` (default) - columns are `LazyParsed` enums
///   that may be raw or decoded. Use [`Layer01::decode_all`] to transition to `Layer01<Parsed>`.
///
/// - `Layer01<'a, Parsed>` - all columns are fully decoded. The fields `id`, `geometry`, and
///   `properties` hold the parsed types directly, allowing infallible readonly access.
///   There is a `ParsedLayer01<'a>` type alias for this.
#[derive(Debug, Clone)]
pub struct Layer01<'a, S: DecodeState = Lazy> {
    pub(crate) name: &'a str,
    pub(crate) extent: Extent,
    pub(crate) id: Option<Id<'a, S>>,
    pub(crate) geometry: Geometry<'a, S>,
    pub(crate) properties: Vec<Property<'a, S>>,
    #[cfg(fuzzing)]
    pub(crate) layer_order: Vec<crate::decoder::fuzzing::LayerOrdering>,
}

pub type ParsedLayer01<'a> = Layer01<'a, Parsed>;

impl<'a, S: DecodeState> Layer01<'a, S> {
    #[must_use]
    pub fn name(&self) -> &'a str {
        self.name
    }

    #[must_use]
    pub fn extent(&self) -> Extent {
        self.extent
    }
}

impl ParsedLayer01<'_> {
    /// Returns the decoded geometry buffer for this layer.
    ///
    /// Provides access to the columnar geometry arrays (vertex buffer, offset arrays, geometry
    /// types) for advanced use cases such as building typed arrays for WebAssembly or
    /// performing spatial indexing. For iterating feature geometries as `geo_types` values,
    /// prefer [`iter_features`](Self::iter_features) instead.
    #[must_use]
    pub fn geometry_values(&self) -> &GeometryValues {
        &self.geometry
    }

    #[must_use]
    pub fn feature_count(&self) -> usize {
        self.geometry.vector_types.len()
    }

    /// The layer's property columns, in wire order.
    #[must_use]
    pub fn properties(&self) -> &[ParsedProperty<'_>] {
        &self.properties
    }
}

/// Column definition
#[derive(Debug, PartialEq)]
pub struct Column<'a> {
    pub(crate) typ: ColumnType,
    pub(crate) name: Option<&'a str>,
    pub(crate) children: Vec<Self>,
}

/// Column data type, as stored in the tile.
#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum ColumnType {
    Id = 0b0000_0000,
    OptId = 0b0000_0001,
    LongId = 0b0000_0010,
    OptLongId = 0b0000_0011,
    Geometry = 0b0000_0100,
    Bool = 0b0000_1010,
    OptBool = 0b0000_1011,
    I32 = 0b0001_0000,
    OptI32 = 0b0001_0001,
    U32 = 0b0001_0010,
    OptU32 = 0b0001_0011,
    I64 = 0b0001_0100,
    OptI64 = 0b0001_0101,
    U64 = 0b0001_0110,
    OptU64 = 0b0001_0111,
    F32 = 0b0001_1000,
    OptF32 = 0b0001_1001,
    F64 = 0b0001_1010,
    OptF64 = 0b0001_1011,
    Str = 0b0001_1100,
    OptStr = 0b0001_1101,
    SharedDict = 0b0001_1110,
}

impl Column<'_> {
    /// Parse a single column definition
    pub(crate) fn from_bytes<'a>(
        input: &'a [u8],
        _parser: &mut Parser,
    ) -> MltRefResult<'a, Column<'a>> {
        let (mut input, typ) = ColumnType::from_bytes(input)?;
        let name = if typ.has_name() {
            let pair = parse_string(input)?;
            input = pair.0;
            Some(pair.1)
        } else {
            None
        };

        Ok((
            input,
            Column {
                typ,
                name,
                children: Vec::new(),
            },
        ))
    }
}

impl ColumnType {
    /// Parse a column type from u8
    pub(crate) fn from_bytes(input: &[u8]) -> MltRefResult<'_, Self> {
        let (input, value) = parse_u8(input)?;
        let value = Self::try_from(value).or(Err(ParsingColumnType(value)))?;
        Ok((input, value))
    }

    pub(crate) fn write_to<W: Write>(self, writer: &mut W) -> io::Result<()> {
        writer.write_u8(self as u8)?;
        Ok(())
    }

    /// Returns true if the column definition includes a name field in the serialized format.
    /// Note: ID and Geometry columns use implicit naming and do not include a name field.
    #[must_use]
    pub(crate) fn has_name(self) -> bool {
        !matches!(
            self,
            Self::Id | Self::OptId | Self::LongId | Self::OptLongId | Self::Geometry
        )
    }

    /// Check if the column type has a presence stream
    #[must_use]
    pub(crate) fn is_optional(self) -> bool {
        (self as u8) & OPTIONAL_FLAG != 0
    }
}

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

    use super::ColumnType;
    use crate::MltError;

    #[rstest]
    fn column_type_rejects_unassigned(#[values(5, 6, 7, 8, 9, 12, 13, 14, 15, 31)] byte: u8) {
        let err = ColumnType::from_bytes(&[byte]).unwrap_err();
        assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
    }
}