jigs-map 0.3.1

HTML map generator for jigs pipelines
Documentation
use jigs_core::JigMeta;
use std::collections::BTreeMap;

pub(crate) type Index = BTreeMap<&'static str, Vec<&'static JigMeta>>;

pub(crate) fn build_index(jigs: impl Iterator<Item = &'static JigMeta>) -> Index {
    let mut map: Index = BTreeMap::new();
    for m in jigs {
        map.entry(m.name).or_default().push(m);
    }
    map
}

pub(crate) fn resolve(name: &str, all: &Index) -> Option<&'static JigMeta> {
    if let Some(v) = all.get(name) {
        return v
            .iter()
            .max_by_key(|m| m.module.split("::").count())
            .copied();
    }
    if let Some(pos) = name.rfind("::") {
        let target_name = &name[pos + 2..];
        let prefix = name[..pos].strip_prefix("crate::").unwrap_or(&name[..pos]);
        if let Some(candidates) = all.get(target_name) {
            for m in candidates {
                if m.module == prefix
                    || m.module.ends_with(&format!("::{prefix}"))
                    || m.module.contains(&format!("::{prefix}"))
                {
                    return Some(m);
                }
            }
            for m in candidates {
                if m.file.contains(prefix) {
                    return Some(m);
                }
            }
            return candidates.first().copied();
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_meta(name: &'static str, module: &'static str) -> JigMeta {
        JigMeta {
            name,
            file: "",
            line: 0,
            kind: "Response",
            input: "Request",
            input_type: "",
            output_type: "",
            is_async: false,
            module,
            chain: &[],
        }
    }

    fn leak_meta(items: Vec<JigMeta>) -> Index {
        let mut map: Index = BTreeMap::new();
        for m in items {
            let r: &'static JigMeta = Box::leak(Box::new(m));
            map.entry(r.name).or_default().push(r);
        }
        map
    }

    #[test]
    fn resolve_picks_deepest_module_when_names_collide() {
        let all = leak_meta(vec![
            make_meta("validate", "crate"),
            make_meta("validate", "crate::features::auth"),
        ]);
        let resolved = resolve("validate", &all);
        assert_eq!(resolved.unwrap().module, "crate::features::auth");
    }

    #[test]
    fn resolve_qualified_matches_module_prefix() {
        let all = leak_meta(vec![
            make_meta("validate", "crate"),
            make_meta("validate", "crate::features::auth"),
        ]);
        let resolved = resolve("features::auth::validate", &all);
        assert_eq!(resolved.unwrap().module, "crate::features::auth");
    }

    #[test]
    fn resolve_does_not_match_suffix_without_boundary() {
        let all = leak_meta(vec![
            make_meta("handle", "crate::features::oauth"),
            make_meta("handle", "crate::features::auth"),
        ]);
        let resolved = resolve("auth::handle", &all);
        assert_eq!(resolved.unwrap().module, "crate::features::auth");
    }

    #[test]
    fn resolve_exact_module_match() {
        let all = leak_meta(vec![make_meta("handle", "auth")]);
        let resolved = resolve("auth::handle", &all);
        assert_eq!(resolved.unwrap().module, "auth");
    }
}