packed_spatial_index/persistence/
metadata.rs1use super::container::{find_chunk, parse_container};
2use super::{ByteWriter, LoadError, TAG_TREE, read_u16_at, read_u32_at};
3
4pub(crate) const TAG_META: [u8; 4] = *b"META";
7
8const META_CRS: u16 = 0;
12const META_CONTENT_TYPE: u16 = 1;
13const META_ATTRIBUTION: u16 = 2;
14
15#[derive(Default)]
17pub(crate) struct MetaFields<'a> {
18 pub(crate) crs: Option<&'a str>,
19 pub(crate) content_type: Option<&'a str>,
20 pub(crate) attribution: Option<&'a str>,
21}
22
23impl MetaFields<'_> {
24 pub(crate) fn is_empty(&self) -> bool {
25 self.crs.is_none() && self.content_type.is_none() && self.attribution.is_none()
26 }
27
28 pub(crate) fn content_len(&self) -> usize {
30 [self.crs, self.content_type, self.attribution]
31 .into_iter()
32 .flatten()
33 .map(|s| 6 + s.len()) .sum()
35 }
36}
37
38#[derive(Clone, Debug, Default, PartialEq, Eq)]
42pub struct FileMetadata {
43 pub crs: Option<String>,
45 pub content_type: Option<String>,
47 pub attribution: Option<String>,
49}
50
51impl ByteWriter<'_> {
52 pub(crate) fn write_meta(&mut self, fields: &MetaFields<'_>) {
53 for (id, value) in [
54 (META_CRS, fields.crs),
55 (META_CONTENT_TYPE, fields.content_type),
56 (META_ATTRIBUTION, fields.attribution),
57 ] {
58 if let Some(s) = value {
59 self.write_u16(id);
60 self.write_u32(s.len() as u32);
61 self.write_bytes(s.as_bytes());
62 }
63 }
64 }
65}
66
67pub fn read_metadata(bytes: &[u8]) -> Result<FileMetadata, LoadError> {
71 let chunks = parse_container(bytes, &[TAG_TREE])?;
72 match find_chunk(&chunks, TAG_META) {
73 Some(m) => parse_meta(&bytes[m.offset..m.offset + m.len]),
74 None => Ok(FileMetadata::default()),
75 }
76}
77
78fn parse_meta(content: &[u8]) -> Result<FileMetadata, LoadError> {
80 let mut md = FileMetadata::default();
81 let mut off = 0;
82 while off < content.len() {
83 let id = read_u16_at(content, off)?;
84 let len = read_u32_at(content, off + 2)? as usize;
85 let start = off + 6;
86 let end = start.checked_add(len).ok_or(LoadError::IntegerOverflow)?;
87 let bytes = content.get(start..end).ok_or(LoadError::Truncated)?;
88 let s = std::str::from_utf8(bytes).map_err(|_| LoadError::InvalidTree)?;
89 match id {
90 META_CRS => md.crs = Some(s.to_owned()),
91 META_CONTENT_TYPE => md.content_type = Some(s.to_owned()),
92 META_ATTRIBUTION => md.attribution = Some(s.to_owned()),
93 _ => {} }
95 off = end;
96 }
97 Ok(md)
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn meta_parses_known_fields_and_skips_unknown() {
106 let mut content = Vec::new();
109 let put = |c: &mut Vec<u8>, id: u16, value: &[u8]| {
110 c.extend_from_slice(&id.to_le_bytes());
111 c.extend_from_slice(&(value.len() as u32).to_le_bytes());
112 c.extend_from_slice(value);
113 };
114 put(&mut content, 0, b"EPSG:4326"); put(&mut content, 99, b"from-the-future"); put(&mut content, 2, b"attribution-text"); let md = parse_meta(&content).unwrap();
119 assert_eq!(md.crs.as_deref(), Some("EPSG:4326"));
120 assert_eq!(md.attribution.as_deref(), Some("attribution-text"));
121 assert_eq!(md.content_type, None);
122
123 assert_eq!(parse_meta(&[]).unwrap(), FileMetadata::default());
125 }
126}