use std::fmt;
use usize_cast::IntoUsize;
use crate::generated::vector_tile::tile as proto_tile;
use crate::{MvtError, MvtResult, MvtValue};
#[derive(Debug, Clone)]
pub struct MvtPropertyIter<'a> {
keys: &'a [&'a str],
values: &'a [proto_tile::ValueView<'a>],
tags: std::slice::Chunks<'a, u32>,
}
impl<'a> MvtPropertyIter<'a> {
pub(crate) fn new(
keys: &'a [&'a str],
values: &'a [proto_tile::ValueView<'a>],
tags: std::slice::Chunks<'a, u32>,
) -> Self {
Self { keys, values, tags }
}
}
impl<'a> Iterator for MvtPropertyIter<'a> {
type Item = MvtResult<(&'a str, MvtValueRef<'a>)>;
fn next(&mut self) -> Option<Self::Item> {
let pair = self.tags.next()?;
let [key_idx, value_idx] = pair else {
return Some(Err(MvtError::InvalidTagsLength(pair.len())));
};
let key = match self.keys.get((*key_idx).into_usize()) {
Some(key) => *key,
None => return Some(Err(MvtError::InvalidKeyIndex(*key_idx))),
};
let value = match self.values.get((*value_idx).into_usize()) {
Some(value) => value_ref(value),
None => return Some(Err(MvtError::InvalidValueIndex(*value_idx))),
};
Some(Ok((key, value)))
}
}
#[derive(Copy, Clone, PartialEq)]
pub enum MvtValueRef<'a> {
String(&'a str),
Float(f32),
Double(f64),
Int(i64),
UInt(u64),
SInt(i64),
Bool(bool),
Null,
}
impl MvtValueRef<'_> {
#[must_use]
pub fn into_owned(self) -> MvtValue {
match self {
Self::String(value) => MvtValue::String(value.to_string()),
Self::Float(value) => MvtValue::Float(value),
Self::Double(value) => MvtValue::Double(value),
Self::Int(value) => MvtValue::Int(value),
Self::UInt(value) => MvtValue::UInt(value),
Self::SInt(value) => MvtValue::SInt(value),
Self::Bool(value) => MvtValue::Bool(value),
Self::Null => MvtValue::Null,
}
}
}
impl fmt::Debug for MvtValueRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
MvtValueRef::String(value) => write!(f, "{value:?}"),
MvtValueRef::Float(value) => write!(f, "{value} (float)"),
MvtValueRef::Double(value) => write!(f, "{value} (double)"),
MvtValueRef::Int(value) => write!(f, "{value} (int)"),
MvtValueRef::SInt(value) => write!(f, "{value} (sint)"),
MvtValueRef::UInt(value) => write!(f, "{value} (uint)"),
MvtValueRef::Bool(value) => write!(f, "{value} (bool)"),
MvtValueRef::Null => f.write_str("null"),
}
}
}
pub(crate) fn value_ref<'a>(value: &'a proto_tile::ValueView<'a>) -> MvtValueRef<'a> {
if let Some(value) = value.string_value {
MvtValueRef::String(value)
} else if let Some(value) = value.float_value {
MvtValueRef::Float(value)
} else if let Some(value) = value.double_value {
MvtValueRef::Double(value)
} else if let Some(value) = value.int_value {
MvtValueRef::Int(value)
} else if let Some(value) = value.uint_value {
MvtValueRef::UInt(value)
} else if let Some(value) = value.sint_value {
MvtValueRef::SInt(value)
} else if let Some(value) = value.bool_value {
MvtValueRef::Bool(value)
} else {
MvtValueRef::Null
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MvtReaderRef;
use crate::reader::tests::{encode_layer, first_feature};
#[test]
fn value_ref_debug_renders_bare_values() {
insta::assert_debug_snapshot!(MvtValueRef::String("x"), @r#""x""#);
insta::assert_debug_snapshot!(MvtValueRef::Float(1.25), @"1.25 (float)");
insta::assert_debug_snapshot!(MvtValueRef::Double(2.5), @"2.5 (double)");
insta::assert_debug_snapshot!(MvtValueRef::Int(-3), @"-3 (int)");
insta::assert_debug_snapshot!(MvtValueRef::UInt(4), @"4 (uint)");
insta::assert_debug_snapshot!(MvtValueRef::SInt(-5), @"-5 (sint)");
insta::assert_debug_snapshot!(MvtValueRef::Bool(true), @"true (bool)");
insta::assert_debug_snapshot!(MvtValueRef::Null, @"null");
}
#[test]
fn value_ref_into_owned_covers_every_variant() {
assert_eq!(
MvtValueRef::String("x").into_owned(),
MvtValue::String("x".into())
);
assert_eq!(MvtValueRef::Float(1.25).into_owned(), MvtValue::Float(1.25));
assert_eq!(MvtValueRef::Double(2.5).into_owned(), MvtValue::Double(2.5));
assert_eq!(MvtValueRef::Int(-3).into_owned(), MvtValue::Int(-3));
assert_eq!(MvtValueRef::UInt(4).into_owned(), MvtValue::UInt(4));
assert_eq!(MvtValueRef::SInt(-5).into_owned(), MvtValue::SInt(-5));
assert_eq!(MvtValueRef::Bool(true).into_owned(), MvtValue::Bool(true));
assert_eq!(MvtValueRef::Null.into_owned(), MvtValue::Null);
}
#[test]
fn property_iterator_reports_malformed_tags() {
let layer = proto_tile::Layer {
version: 2,
name: "tags".into(),
keys: vec!["k".into()],
values: vec![proto_tile::Value::default()],
..Default::default()
};
for (tags, expected) in [
(vec![0], "invalid feature tags length: 1"),
(vec![1, 0], "invalid key index 1"),
(vec![0, 1], "invalid value index 1"),
] {
let feature = proto_tile::Feature {
tags,
..Default::default()
};
let mut layer = layer.clone();
layer.features = vec![feature];
let bytes = encode_layer(layer);
let reader = MvtReaderRef::new(&bytes).unwrap();
let err = first_feature(&reader)
.properties()
.next()
.unwrap()
.unwrap_err();
assert_eq!(err.to_string(), expected);
}
}
}