packageurl 0.6.0

Rust implementation of the package url specification
Documentation
use super::errors::Error;
use super::errors::Result;
use super::utils;
use super::utils::PercentCodec;
use super::utils::QuickFind;
use super::validation;

pub fn parse_scheme(input: &str) -> Result<(&str, String)> {
    if let Some(i) = input.quickfind(b':') {
        if &input[..i] == "pkg" {
            let mut j = i + 1;
            let mut it = input[i + 1..].chars();
            while let Some('/') = it.next() {
                j += 1;
            }

            Ok((&input[j..], input[..i].to_string()))
        } else {
            Err(Error::InvalidScheme(input[..i].to_string()))
        }
    } else {
        Err(Error::MissingScheme)
    }
}

pub fn parse_subpath(input: &str) -> Result<(&str, Option<String>)> {
    if let Some(i) = input.quickrfind(b'#') {
        let mut subpath = String::with_capacity(i + 1);
        let mut components = input[i + 1..]
            .trim_matches('/')
            .split('/')
            .filter(|&c| !(c.is_empty() || c == "." || c == ".."));
        if let Some(c) = components.next() {
            let decoded = c.decode().decode_utf8()?;
            if validation::is_subpath_segment_valid(&decoded) {
                subpath.push_str(&decoded);
            } else {
                return Err(Error::InvalidSubpathSegment(decoded.to_string()));
            }
        }
        for c in components {
            let decoded = c.decode().decode_utf8()?;
            if validation::is_subpath_segment_valid(&decoded) {
                subpath.push('/');
                subpath.push_str(&decoded);
            } else {
                return Err(Error::InvalidSubpathSegment(decoded.to_string()));
            }
        }
        Ok((&input[..i], Some(subpath)))
    } else {
        Ok((input, None))
    }
}

pub fn parse_qualifiers(input: &str) -> Result<(&str, Vec<(String, String)>)> {
    if let Some(i) = input.quickrfind(b'?') {
        let mut qualifiers = Vec::new();
        let pairs = input[i + 1..]
            .split('&')
            .map(|pair| utils::cut(pair, b'='))
            .filter(|pair| !pair.1.is_empty());
        for (key, value) in pairs {
            if validation::is_qualifier_key_valid(key) {
                qualifiers.push((
                    key.to_lowercase(),
                    value.decode().decode_utf8()?.to_string(),
                ))
            } else {
                return Err(Error::InvalidKey(key.to_string()));
            }
        }
        Ok((&input[..i], qualifiers))
    } else {
        Ok((input, Vec::new()))
    }
}

pub fn parse_version(input: &str) -> Result<(&str, Option<String>)> {
    if let Some(i) = input.quickrfind(b'@') {
        Ok((
            &input[..i],
            Some(input[i + 1..].decode().decode_utf8()?.into()),
        ))
    } else {
        Ok((input, None))
    }
}

pub fn parse_type(input: &str) -> Result<(&str, String)> {
    match input.quickfind(b'/') {
        Some(i) if validation::is_type_valid(&input[..i]) => {
            Ok((&input[i + 1..], input[..i].to_lowercase()))
        }
        Some(i) => Err(Error::InvalidType(input[..i].to_string())),
        None => Err(Error::MissingType),
    }
}

pub fn parse_name(input: &str) -> Result<(&str, String)> {
    let (rem, name) = utils::rcut(input.trim_matches('/'), b'/');
    if name.is_empty() {
        Err(Error::MissingName)
    } else {
        let canonical_name = name.decode().decode_utf8()?.to_string();
        Ok((rem, canonical_name))
    }
}

pub fn parse_namespace(input: &str) -> Result<(&str, Option<String>)> {
    if !input.is_empty() {
        let mut namespace = String::with_capacity(input.len());
        let mut components = input
            .trim_matches('/')
            .split('/')
            .filter(|&c| !(c.is_empty() || c == "." || c == ".."));
        if let Some(c) = components.next() {
            let decoded = c.decode().decode_utf8()?;
            if validation::is_namespace_component_valid(&decoded) {
                namespace.push_str(&decoded);
            } else {
                return Err(Error::InvalidNamespaceComponent(decoded.to_string()));
            }
        }
        for c in components {
            let decoded = c.decode().decode_utf8()?;
            if validation::is_namespace_component_valid(&decoded) {
                namespace.push('/');
                namespace.push_str(&decoded);
            } else {
                return Err(Error::InvalidNamespaceComponent(decoded.to_string()));
            }
        }
        Ok(("", Some(namespace)))
    } else {
        Ok(("", None))
    }
}