osm_tags/
lib.rs

1//! OSM Tags
2//!
3//! Provides `Tags`, `TagKey`, and `TagVal` structures to represent and help manipulate OpenStreetMap tags
4
5#![warn(explicit_outlives_requirements)]
6#![warn(missing_abi)]
7#![deny(non_ascii_idents)]
8#![warn(trivial_casts)]
9#![warn(unreachable_pub)]
10#![deny(unsafe_code)]
11#![deny(unsafe_op_in_unsafe_fn)]
12// #![warn(unused_crate_dependencies)] // https://github.com/rust-lang/rust/issues/57274
13#![warn(unused_lifetimes)]
14#![warn(unused_qualifications)]
15// Clippy
16#![warn(clippy::pedantic, clippy::cargo)]
17#![allow(clippy::missing_panics_doc)]
18#![allow(clippy::cargo_common_metadata)]
19#![warn(
20    clippy::allow_attributes_without_reason,
21    clippy::as_conversions,
22    clippy::clone_on_ref_ptr,
23    clippy::create_dir,
24    clippy::dbg_macro,
25    clippy::decimal_literal_representation,
26    clippy::default_numeric_fallback,
27    clippy::deref_by_slicing,
28    clippy::empty_structs_with_brackets,
29    clippy::float_cmp_const,
30    clippy::fn_to_numeric_cast_any,
31    clippy::if_then_some_else_none,
32    clippy::indexing_slicing,
33    clippy::let_underscore_must_use,
34    clippy::map_err_ignore,
35    clippy::print_stderr,
36    clippy::print_stdout,
37    clippy::single_char_lifetime_names,
38    clippy::str_to_string,
39    clippy::string_add,
40    clippy::string_slice,
41    clippy::string_to_string,
42    clippy::todo,
43    clippy::try_err,
44    clippy::unseparated_literal_suffix,
45    clippy::use_debug
46)]
47
48use std::borrow::Borrow;
49use std::collections::btree_map::Entry;
50use std::collections::BTreeMap;
51use std::hash::Hash;
52use std::str::FromStr;
53
54mod key;
55pub use key::TagKey;
56
57mod val;
58pub use val::TagVal;
59
60#[derive(Debug, Clone)]
61pub struct DuplicateKeyError(TagKey);
62
63impl From<String> for DuplicateKeyError {
64    fn from(string: String) -> Self {
65        DuplicateKeyError(TagKey::from(&string))
66    }
67}
68
69impl std::fmt::Display for DuplicateKeyError {
70    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
71        write!(f, "duplicate tag key {}", self.0.as_str())
72    }
73}
74
75impl std::error::Error for DuplicateKeyError {}
76
77/// A map from string keys to string values. This makes copies of strings for
78/// convenience; don't use in performance sensitive contexts.
79//
80// BTreeMap chosen for deterministic serialization.
81// We often need to compare output directly, so cannot tolerate reordering
82//
83// TODO: fix this in the serialization by having the keys sorted.
84#[derive(Clone, Debug, Default)]
85pub struct Tags {
86    map: BTreeMap<TagKey, TagVal>,
87}
88
89impl Tags {
90    /// Construct from iterator of pairs
91    ///
92    /// # Errors
93    ///
94    /// If a duplicate key is provided.
95    ///
96    pub fn from_pairs<I, K, V>(tags: I) -> Result<Self, DuplicateKeyError>
97    where
98        I: IntoIterator<Item = (K, V)>,
99        K: Into<TagKey>,
100        V: Into<TagVal>,
101    {
102        let mut map: BTreeMap<TagKey, TagVal> = BTreeMap::new();
103        for tag_pair in tags {
104            let key: TagKey = tag_pair.0.into();
105            let val: TagVal = tag_pair.1.into();
106            // This may become cleaner with https://github.com/rust-lang/rust/issues/82766
107            match map.entry(key) {
108                Entry::Vacant(entry) => entry.insert(val),
109                Entry::Occupied(entry) => return Err(DuplicateKeyError(entry.remove_entry().0)),
110            };
111        }
112        Ok(Self { map })
113    }
114
115    /// Construct from pair
116    #[must_use]
117    pub fn from_pair<K, V>(key: K, val: V) -> Self
118    where
119        K: Into<TagKey>,
120        V: Into<TagVal>,
121    {
122        let mut map = BTreeMap::default();
123        let duplicate_val = map.insert(key.into(), val.into());
124        debug_assert!(duplicate_val.is_none());
125        Self { map }
126    }
127
128    /// Expose data as vector of pairs
129    #[must_use]
130    pub fn is_empty(&self) -> bool {
131        self.map.is_empty()
132    }
133
134    /// Expose data as vector of pairs
135    #[must_use]
136    pub fn to_str_pairs(&self) -> Vec<(&str, &str)> {
137        self.map
138            .iter()
139            .map(|(k, v)| (k.as_str(), v.as_str()))
140            .collect()
141    }
142
143    /// Vector of `=` separated strings
144    #[must_use]
145    pub fn to_vec(&self) -> Vec<String> {
146        let pairs = self.to_str_pairs();
147        pairs
148            .into_iter()
149            .map(|(key, val)| format!("{key}={val}"))
150            .collect()
151    }
152
153    /// Get value from tags given a key
154    pub fn get<Q>(&self, q: &Q) -> Option<&str>
155    where
156        TagKey: Borrow<Q>,
157        Q: Ord + Hash + Eq + ?Sized,
158    {
159        self.map.get(q).map(TagVal::as_str)
160    }
161
162    /// Return if tags key has value,
163    /// return false if key does not exist.
164    #[must_use]
165    pub fn is<Q>(&self, q: &Q, v: &str) -> bool
166    where
167        TagKey: Borrow<Q>,
168        Q: Ord + Hash + Eq + ?Sized,
169    {
170        self.get(q) == Some(v)
171    }
172
173    /// Return if tags key has any of the values,
174    /// return false if the key does not exist.
175    #[must_use]
176    pub fn is_any<Q>(&self, q: &Q, values: &[&str]) -> bool
177    where
178        TagKey: Borrow<Q>,
179        Q: Ord + Hash + Eq + ?Sized,
180    {
181        if let Some(v) = self.get(q) {
182            values.contains(&v)
183        } else {
184            false
185        }
186    }
187
188    /// Get a subset of the tags
189    #[must_use]
190    pub fn subset<'any, I, Q, O>(&self, keys: I) -> Self
191    where
192        I: IntoIterator<Item = &'any Q>,
193        TagKey: Borrow<Q>,
194        Q: 'any + Ord + Hash + Eq + ?Sized + ToOwned<Owned = O>,
195        O: Into<TagKey>,
196    {
197        let mut map = Self::default();
198        for key in keys {
199            if let Some(val) = self.get(key) {
200                let owned: O = key.to_owned();
201                let insert = map.checked_insert(owned.into(), TagVal::from(val));
202                debug_assert!(insert.is_ok());
203            }
204        }
205        map
206    }
207
208    /// Get node given a key part
209    pub fn pairs_with_stem<Q>(&self, q: &Q) -> Vec<(&str, &str)>
210    where
211        Q: AsRef<str> + ?Sized,
212    {
213        self.map
214            .iter()
215            .filter_map(|(key, val)| {
216                key.as_str()
217                    .starts_with(q.as_ref())
218                    .then(|| (key.as_str(), val.as_str()))
219            })
220            .collect()
221    }
222
223    /// # Errors
224    ///
225    /// If duplicate key is inserted.   
226    ///
227    pub fn checked_insert<K: Into<TagKey>, V: Into<TagVal>>(
228        &mut self,
229        key: K,
230        val: V,
231    ) -> Result<(), DuplicateKeyError> {
232        let key: TagKey = key.into();
233        // This may become cleaner with https://github.com/rust-lang/rust/issues/82766
234        match self.map.entry(key) {
235            Entry::Vacant(entry) => entry.insert(val.into()),
236            Entry::Occupied(entry) => return Err(DuplicateKeyError(entry.remove_entry().0)),
237        };
238        Ok(())
239    }
240}
241
242#[derive(Debug)]
243pub enum ParseTagsError {
244    MissingEquals(String),
245    DuplicateKey(DuplicateKeyError),
246}
247
248impl std::fmt::Display for ParseTagsError {
249    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
250        match self {
251            Self::MissingEquals(_val) => write!(f, "tag must be = separated"),
252            Self::DuplicateKey(duplicate_key_err) => duplicate_key_err.fmt(f),
253        }
254    }
255}
256
257impl std::error::Error for ParseTagsError {}
258
259impl FromStr for Tags {
260    type Err = ParseTagsError;
261
262    /// Parse '=' separated tag pairs from a newline separated list.
263    ///
264    /// ```
265    /// use std::str::FromStr;
266    /// use osm_tags::Tags;
267    /// let tags = Tags::from_str("foo=bar\nabra=cadabra").unwrap();
268    /// assert_eq!(tags.get("foo"), Some("bar"));
269    /// ```
270    fn from_str(s: &str) -> Result<Self, Self::Err> {
271        let tags = s
272            .lines()
273            .map(|line| {
274                let (key, val) = line
275                    .split_once('=')
276                    .ok_or_else(|| ParseTagsError::MissingEquals(line.to_owned()))?;
277                Ok((key.to_owned(), val.to_owned()))
278            })
279            .collect::<Result<Vec<(String, String)>, Self::Err>>()?;
280        Self::from_pairs(tags).map_err(ParseTagsError::DuplicateKey)
281    }
282}
283
284impl ToString for Tags {
285    /// Return tags as an '=' separated list
286    ///
287    /// ```
288    /// use std::str::FromStr;
289    /// use std::string::ToString;
290    /// use osm_tags::Tags;
291    /// let tags = Tags::from_str("foo=bar\nabra=cadabra").unwrap();
292    /// assert_eq!(tags.to_string(), "abra=cadabra\nfoo=bar");
293    /// ```
294    fn to_string(&self) -> String {
295        self.to_vec().as_slice().join("\n")
296    }
297}
298
299/// A Visitor holds methods that a Deserializer can drive
300#[cfg(feature = "serde")]
301struct TagsVisitor {
302    marker: std::marker::PhantomData<fn() -> Tags>,
303}
304
305#[cfg(feature = "serde")]
306impl TagsVisitor {
307    fn new() -> Self {
308        TagsVisitor {
309            marker: std::marker::PhantomData,
310        }
311    }
312}
313
314/// Visitor to Deserialize of Tags
315#[cfg(feature = "serde")]
316impl<'de> serde::de::Visitor<'de> for TagsVisitor {
317    type Value = Tags;
318    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
319        formatter.write_str("OSM Tags as Map")
320    }
321    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
322    where
323        M: serde::de::MapAccess<'de>,
324    {
325        let mut tags = Tags::default();
326        while let Some((key, value)) = access.next_entry::<String, String>()? {
327            // Overpass sometimes returns duplicate tags
328            let _ignored = tags.checked_insert(&key, value);
329        }
330        Ok(tags)
331    }
332}
333
334/// Informs Serde how to deserialize Tags
335#[cfg(feature = "serde")]
336impl<'de> serde::Deserialize<'de> for Tags {
337    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
338    where
339        D: serde::de::Deserializer<'de>,
340    {
341        // Instantiate our Visitor and ask the Deserializer to drive
342        // it over the input data, resulting in an instance of MyMap.
343        deserializer.deserialize_map(TagsVisitor::new())
344    }
345}
346
347#[cfg(feature = "serde")]
348impl serde::Serialize for Tags {
349    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
350    where
351        S: serde::Serializer,
352    {
353        use serde::ser::SerializeMap;
354        let mut map = serializer.serialize_map(Some(self.map.len()))?;
355        for (k, v) in &self.map {
356            map.serialize_entry(k.as_str(), v.as_str())?;
357        }
358        map.end()
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use crate::{DuplicateKeyError, TagKey, Tags};
365
366    #[test]
367    fn test_tags() {
368        let tags = Tags::from_pairs([
369            ("foo", "bar"),
370            ("abra", "cadabra"),
371            ("foo:multi:key", "value"),
372            ("multivalue", "apple;banana;chocolate covered capybara"),
373        ])
374        .unwrap();
375        assert_eq!(
376            tags.to_vec(),
377            vec![
378                "abra=cadabra",
379                "foo=bar",
380                "foo:multi:key=value",
381                "multivalue=apple;banana;chocolate covered capybara"
382            ]
383        );
384
385        // Serde
386        let tags_str = "{\"abra\":\"cadabra\",\"foo\":\"bar\",\"foo:multi:key\":\"value\",\"multivalue\":\"apple;banana;chocolate covered capybara\"}";
387        assert_eq!(serde_json::to_string(&tags).unwrap(), tags_str);
388        let de_tags: Tags = serde_json::from_str(tags_str).unwrap();
389        assert_eq!(de_tags.to_str_pairs(), tags.to_str_pairs());
390
391        // Misc
392        let mut other_tags = tags.clone();
393        assert!(other_tags.checked_insert("new", "val").is_ok());
394        assert!(matches!(
395            other_tags.checked_insert("foo", "bar").unwrap_err(),
396            DuplicateKeyError(_),
397        ));
398        assert!(other_tags
399            .checked_insert(String::from("owned"), "val")
400            .is_ok());
401
402        // String interfaces
403        assert_eq!(tags.get("foo"), Some("bar"));
404        assert_eq!(tags.get("bar"), None);
405        assert!(tags.is("foo", "bar"));
406        assert!(!tags.is("foo", "foo"));
407        assert!(!tags.is("bar", "foo"));
408        assert!(tags.is_any("foo", &["bar"]));
409        assert!(tags.is_any("foo", &["foo", "bar"]));
410        assert!(!tags.is_any("foo", &["foo"]));
411        assert!(!tags.is_any("bar", &["foo", "bar"]));
412        assert_eq!(tags.subset(["foo"]).to_vec(), vec!["foo=bar"]);
413        assert_eq!(
414            tags.subset(["foo", "abra"]).to_vec(),
415            vec!["abra=cadabra", "foo=bar"]
416        );
417        assert_eq!(tags.subset(["foo", "bar"]).to_vec(), vec!["foo=bar"]);
418        assert!(tags.subset(["bar"]).to_vec().is_empty());
419
420        // Key interfaces
421        const FOO_KEY: TagKey = TagKey::from_static("foo");
422        assert_eq!(tags.get(&FOO_KEY), Some("bar"));
423        assert_eq!(tags.get(&TagKey::from_static("bar")), None);
424        assert!(tags.is(&FOO_KEY, "bar"));
425        assert!(!tags.is(&FOO_KEY, "foo"));
426        assert_eq!(tags.subset(&[FOO_KEY]).to_vec(), vec!["foo=bar"]);
427        dbg!(&(FOO_KEY + "multi" + "key"));
428        assert!(tags.is(&(FOO_KEY + "multi" + "key"), "value"));
429        let foo_key = FOO_KEY + "multi" + "key";
430        assert!(tags.is(&foo_key, "value"));
431
432        // Tree interfaces
433        assert_eq!(tags.pairs_with_stem(&FOO_KEY).len(), 2);
434        assert_eq!(tags.pairs_with_stem(&(FOO_KEY + "multi")).len(), 1);
435
436        // TODO: Multi Value
437    }
438}