dae_parser/
url.rs

1use std::{
2    fmt::{Debug, Display},
3    str::FromStr,
4};
5
6use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
7
8/// https://url.spec.whatwg.org/#fragment-percent-encode-set
9const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
10
11/// A (really) basic URL parser.
12///
13/// It is optimized for the case of COLLADA documents where most "URLs"
14/// are really just names of other entities in the document, prefixed by `#`.
15/// Unfortunately the `url` crate does not like these fragments,
16/// and we don't have a base URL to work from since the parser does not do URL resolution.
17/// So we parse fragments and leave everything else to be parsed by a proper URL crate
18/// during resolution.
19#[derive(Clone, PartialEq, Eq)]
20pub enum Url {
21    /// A fragment `#foo`. The string is the percent-decoded payload `"foo"`.
22    Fragment(String),
23    /// A maybe-URL which is not a fragment. These are unparsed and unvalidated.
24    Other(String),
25}
26
27impl FromStr for Url {
28    type Err = std::str::Utf8Error;
29
30    fn from_str(s: &str) -> Result<Self, Self::Err> {
31        let mut it = s.chars();
32        Ok(if it.next() == Some('#') {
33            Url::Fragment(percent_decode_str(it.as_str()).decode_utf8()?.into())
34        } else {
35            Url::Other(s.into())
36        })
37    }
38}
39
40impl Display for Url {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            Self::Fragment(s) => write!(f, "#{}", percent_encode(s.as_bytes(), FRAGMENT)),
44            Self::Other(s) => write!(f, "{}", s),
45        }
46    }
47}
48
49impl Debug for Url {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        Display::fmt(self, f)
52    }
53}