burgerlingual 1.2.0

burger utility library for web localisation
Documentation
use language_tags::{LanguageTag, ValidationError};
use std::cmp::Ordering;
use std::vec::Vec;

#[allow(clippy::cast_possible_truncation)]
pub fn parse_language_weight<S: AsRef<str>>(weight: S) -> Option<u16> {
    let weight = weight.as_ref();
    if weight.len() == 2 {
        None
    } else if let Some(position) = weight.find('.') {
        if position != 1 {
            None
        } else if let Some(exponent) = 5usize.checked_sub(weight.len()) {
                if exponent <= 5 {
                    let result = weight.replace('.', "").parse::<u16>().ok();
                    if exponent > 0 {
                        result.map(|number| {
                            // This is always safe unless we are using a 2-bit target as we check if it's <= 5
                            number * 10u16.pow(exponent as u32)
                        })
                    } else {
                        result
                    }
                } else {
                    None
                }
        } else {
            None
        }
    } else {
        match weight {
            "1" => Some(1000),
            "0" => Some(0),
            _ => None
        }
    }
}

#[test]
pub fn test_parse_http_header_weight() {
    assert_eq!(parse_language_weight("0.5"), Some(500));
    assert_eq!(parse_language_weight("1.000"), Some(1000));
    assert_eq!(parse_language_weight("0.000"), Some(0));
    assert_eq!(parse_language_weight("0.001"), Some(1));
    assert_eq!(parse_language_weight("1"), Some(1000));
    assert_eq!(parse_language_weight("0"), Some(0));
    assert_eq!(parse_language_weight(".0"), None);
    assert_eq!(parse_language_weight("0."), None);
    assert_eq!(parse_language_weight("1."), None);
    assert_eq!(parse_language_weight("1.0"), Some(1000));
    assert_eq!(parse_language_weight("0.0"), Some(0));
    assert_eq!(parse_language_weight("a"), None);
}

#[derive(Debug, PartialEq, Eq)]
pub struct WeightedLanguage(pub LanguageTag, pub u16);

impl PartialOrd<Self> for WeightedLanguage {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for WeightedLanguage {
    fn cmp(&self, other: &Self) -> Ordering {
        self.1.cmp(&other.1)
    }
}

pub type WeightedLanguages = Vec<WeightedLanguage>;

pub fn parse_accept_language_header<S: AsRef<str>>(tag: S) -> Option<WeightedLanguages> {
    tag.as_ref().split(',')
        .map(str::trim)
        .map(|language| {
            language.split(';')
        })
        .map(|mut language| {
            Some((
                language.next()?,
                if let Some(weight) = language.next() {
                    Some(weight.strip_prefix("q=")?)
                } else {
                    None
                }
            ))
        })
        .map(|language| {
            if let Some(language) = language {
                Some(WeightedLanguage(LanguageTag::parse(language.0).ok()?, if let Some(weight) = language.1 {
                    parse_language_weight(weight)?
                } else {
                    1000
                }))
            } else {
                None
            }
        })
        .collect()
}

pub type CanonicalizedWeightedLanguages = WeightedLanguages;
pub fn canonicalize_weighted_languages(weighted_languages: WeightedLanguages) -> Result<CanonicalizedWeightedLanguages, ValidationError> {
    weighted_languages.into_iter()
        .map(|mut language| {
            language.0 = language.0.canonicalize()?;
            Ok(language)
        }).collect::<Result<Vec<_>, _>>()
}

#[test]
pub fn test_parse_accept_language_header() {
    assert_eq!(parse_accept_language_header("en-GB;q=1, en-US;q=0.9, zh-CN"), Some(vec![
        WeightedLanguage(LanguageTag::parse("en-GB").unwrap(), 1000),
        WeightedLanguage(LanguageTag::parse("en-US").unwrap(), 900),
        WeightedLanguage(LanguageTag::parse("zh-CN").unwrap(), 1000),
    ]))
}