facto-core 0.1.0-alpha.1

Core models, registry/forge/runtime clients for facto: real-time package ecosystem data for AI agents
Documentation
use crate::runtimes::Runtime;

struct RuntimeDef {
    id: &'static str,
    name: &'static str,
    endoflife_id: &'static str,
    changelog_pattern: ChangelogPattern,
}

enum ChangelogPattern {
    Format(&'static str),
    None,
}

impl Runtime for RuntimeDef {
    fn id(&self) -> &str {
        self.id
    }

    fn display_name(&self) -> &str {
        self.name
    }

    fn endoflife_id(&self) -> &str {
        self.endoflife_id
    }

    fn changelog_url(&self, cycle: &str) -> Option<String> {
        match &self.changelog_pattern {
            ChangelogPattern::Format(fmt) => Some(fmt.replace("{cycle}", cycle)),
            ChangelogPattern::None => None,
        }
    }
}

pub fn all_runtimes() -> Vec<Box<dyn Runtime>> {
    vec![
        Box::new(RuntimeDef {
            id: "python",
            name: "Python",
            endoflife_id: "python",
            changelog_pattern: ChangelogPattern::Format(
                "https://docs.python.org/3/whatsnew/{cycle}.html",
            ),
        }),
        Box::new(RuntimeDef {
            id: "go",
            name: "Go",
            endoflife_id: "go",
            changelog_pattern: ChangelogPattern::Format("https://go.dev/doc/go{cycle}"),
        }),
        Box::new(RuntimeDef {
            id: "rust",
            name: "Rust",
            endoflife_id: "rust",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "nodejs",
            name: "Node.js",
            endoflife_id: "nodejs",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "ruby",
            name: "Ruby",
            endoflife_id: "ruby",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "php",
            name: "PHP",
            endoflife_id: "php",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "java",
            name: "Eclipse Temurin (Java)",
            endoflife_id: "eclipse-temurin",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "dotnet",
            name: ".NET",
            endoflife_id: "dotnet",
            changelog_pattern: ChangelogPattern::Format(
                "https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-{cycle}",
            ),
        }),
        Box::new(RuntimeDef {
            id: "deno",
            name: "Deno",
            endoflife_id: "deno",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "bun",
            name: "Bun",
            endoflife_id: "bun",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "elixir",
            name: "Elixir",
            endoflife_id: "elixir",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "kotlin",
            name: "Kotlin",
            endoflife_id: "kotlin",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "perl",
            name: "Perl",
            endoflife_id: "perl",
            changelog_pattern: ChangelogPattern::None,
        }),
        Box::new(RuntimeDef {
            id: "scala",
            name: "Scala",
            endoflife_id: "scala",
            changelog_pattern: ChangelogPattern::None,
        }),
    ]
}

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

    #[test]
    fn test_all_runtimes_have_unique_ids() {
        let runtimes = all_runtimes();
        let mut ids: Vec<&str> = runtimes.iter().map(|r| r.id()).collect();
        let len = ids.len();
        ids.sort();
        ids.dedup();
        assert_eq!(ids.len(), len, "duplicate runtime IDs found");
    }

    #[test]
    fn test_python_changelog_url() {
        let runtimes = all_runtimes();
        let python = runtimes.iter().find(|r| r.id() == "python").unwrap();
        assert_eq!(
            python.changelog_url("3.14"),
            Some("https://docs.python.org/3/whatsnew/3.14.html".to_string())
        );
    }

    #[test]
    fn test_rust_has_no_changelog_pattern() {
        let runtimes = all_runtimes();
        let rust = runtimes.iter().find(|r| r.id() == "rust").unwrap();
        assert_eq!(rust.changelog_url("1.80"), None);
    }
}