referer-parser-rs 0.1.0

Rust implementation of [snowplow-referer-parser](https://github.com/snowplow-referer-parser/referer-parser).
Documentation
use referer_parser_rs::Parser;
use serde::Deserialize;
use url::Url;

#[derive(Deserialize)]
struct TestCase {
    spec: String,
    uri: String,
    medium: String,
    source: String,
    term: Option<String>,
    known: bool,
}

fn load_parser_and_cases() -> (Parser, Vec<TestCase>) {
    let parser = Parser::new("tests/referers.yml").expect("failed to load referers.yml");
    let data =
        std::fs::read_to_string("tests/referer-tests.json").expect("failed to read test file");
    let cases: Vec<TestCase> = serde_json::from_str(&data).expect("failed to parse test JSON");
    (parser, cases)
}

#[test]
fn all_known_referers_are_found() {
    let (parser, cases) = load_parser_and_cases();
    for case in cases.iter().filter(|c| c.known) {
        let url =
            Url::parse(&case.uri).unwrap_or_else(|e| panic!("[{}]: invalid URL: {e}", case.spec));
        let entry = parser
            .lookup(&url)
            .unwrap_or_else(|| panic!("[{}]: expected known referer, got None", case.spec));
        assert_eq!(entry.medium, case.medium, "[{}]: wrong medium", case.spec);
        assert_eq!(entry.source, case.source, "[{}]: wrong source", case.spec);
        assert_eq!(
            entry.search_term.as_deref(),
            case.term.as_deref(),
            "[{}]: wrong term",
            case.spec
        );
    }
}

#[test]
fn unknown_referers_return_none() {
    let (parser, cases) = load_parser_and_cases();
    for case in cases.iter().filter(|c| !c.known) {
        let url =
            Url::parse(&case.uri).unwrap_or_else(|e| panic!("[{}]: invalid URL: {e}", case.spec));
        assert!(
            parser.lookup(&url).is_none(),
            "[{}]: expected None for unknown referer",
            case.spec
        );
    }
}