use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Query {
pub idea: String,
pub keywords: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Source {
#[serde(rename = "crates-io", alias = "CratesIo")]
CratesIo,
#[serde(rename = "github", alias = "GitHub")]
GitHub,
#[serde(rename = "npm", alias = "Npm")]
Npm,
#[serde(rename = "pypi", alias = "PyPI")]
PyPI,
#[serde(rename = "hacker-news", alias = "HackerNews")]
HackerNews,
#[serde(rename = "go", alias = "Go")]
Go,
#[serde(rename = "maven", alias = "Maven")]
Maven,
#[serde(rename = "rubygems", alias = "RubyGems")]
RubyGems,
#[serde(rename = "docker-hub", alias = "DockerHub")]
DockerHub,
#[serde(rename = "vscode-marketplace", alias = "VsCodeMarketplace")]
VsCodeMarketplace,
#[serde(rename = "nuget", alias = "NuGet")]
NuGet,
#[serde(rename = "homebrew", alias = "Homebrew")]
Homebrew,
#[serde(rename = "packagist", alias = "Packagist")]
Packagist,
#[serde(rename = "hex", alias = "Hex")]
Hex,
#[serde(rename = "artifact-hub", alias = "ArtifactHub")]
ArtifactHub,
#[serde(rename = "aur", alias = "Aur")]
Aur,
#[serde(rename = "hackage", alias = "Hackage")]
Hackage,
#[serde(rename = "nixpkgs", alias = "Nixpkgs")]
Nixpkgs,
}
impl Source {
pub fn all() -> &'static [Source] {
&[
Self::CratesIo,
Self::GitHub,
Self::Npm,
Self::PyPI,
Self::HackerNews,
Self::Go,
Self::Maven,
Self::RubyGems,
Self::DockerHub,
Self::VsCodeMarketplace,
Self::NuGet,
Self::Homebrew,
Self::Packagist,
Self::Hex,
Self::ArtifactHub,
Self::Aur,
Self::Hackage,
Self::Nixpkgs,
]
}
pub fn kebab_name(self) -> &'static str {
match self {
Self::CratesIo => "crates-io",
Self::GitHub => "github",
Self::Npm => "npm",
Self::PyPI => "pypi",
Self::HackerNews => "hacker-news",
Self::Go => "go",
Self::Maven => "maven",
Self::RubyGems => "rubygems",
Self::DockerHub => "docker-hub",
Self::VsCodeMarketplace => "vscode-marketplace",
Self::NuGet => "nuget",
Self::Homebrew => "homebrew",
Self::Packagist => "packagist",
Self::Hex => "hex",
Self::ArtifactHub => "artifact-hub",
Self::Aur => "aur",
Self::Hackage => "hackage",
Self::Nixpkgs => "nixpkgs",
}
}
}
impl std::str::FromStr for Source {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"crates-io" | "crates_io" | "crates.io" => Ok(Self::CratesIo),
"github" => Ok(Self::GitHub),
"npm" => Ok(Self::Npm),
"pypi" => Ok(Self::PyPI),
"hacker-news" | "hacker_news" | "hackernews" => Ok(Self::HackerNews),
"go" => Ok(Self::Go),
"maven" => Ok(Self::Maven),
"rubygems" => Ok(Self::RubyGems),
"docker-hub" | "docker_hub" | "dockerhub" | "docker" => Ok(Self::DockerHub),
"vscode-marketplace" | "vscode_marketplace" | "vscode" => Ok(Self::VsCodeMarketplace),
"nuget" => Ok(Self::NuGet),
"homebrew" | "brew" => Ok(Self::Homebrew),
"packagist" => Ok(Self::Packagist),
"hex" => Ok(Self::Hex),
"artifact-hub" | "artifact_hub" | "artifacthub" => Ok(Self::ArtifactHub),
"aur" => Ok(Self::Aur),
"hackage" => Ok(Self::Hackage),
"nixpkgs" | "nix" => Ok(Self::Nixpkgs),
_ => {
let valid: Vec<&str> = Self::all().iter().map(|s| s.kebab_name()).collect();
Err(format!("unknown source `{s}`. Valid: {}", valid.join(", ")))
}
}
}
}
impl std::fmt::Display for Source {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CratesIo => f.write_str("crates.io"),
Self::GitHub => f.write_str("GitHub"),
Self::Npm => f.write_str("npm"),
Self::PyPI => f.write_str("PyPI"),
Self::HackerNews => f.write_str("Hacker News"),
Self::Go => f.write_str("Go"),
Self::Maven => f.write_str("Maven"),
Self::RubyGems => f.write_str("RubyGems"),
Self::DockerHub => f.write_str("Docker Hub"),
Self::VsCodeMarketplace => f.write_str("VS Code"),
Self::NuGet => f.write_str("NuGet"),
Self::Homebrew => f.write_str("Homebrew"),
Self::Packagist => f.write_str("Packagist"),
Self::Hex => f.write_str("Hex"),
Self::ArtifactHub => f.write_str("Artifact Hub"),
Self::Aur => f.write_str("AUR"),
Self::Hackage => f.write_str("Hackage"),
Self::Nixpkgs => f.write_str("Nixpkgs"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Match {
pub name: String,
pub source: Source,
pub url: String,
pub description: String,
pub popularity: Option<u64>,
pub similarity: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Saturation {
#[serde(alias = "Open")]
Open,
#[serde(alias = "Crowded")]
Crowded,
#[serde(alias = "Saturated")]
Saturated,
}
impl Saturation {
pub fn exit_code(self) -> i32 {
match self {
Self::Open => 0,
Self::Crowded => 1,
Self::Saturated => 2,
}
}
}
impl std::fmt::Display for Saturation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Open => f.write_str("Open"),
Self::Crowded => f.write_str("Crowded"),
Self::Saturated => f.write_str("Saturated"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Verdict {
pub level: Saturation,
pub headline: String,
pub gaps: Vec<String>,
pub sources_checked: Vec<Source>,
#[serde(default)]
pub sources_failed: Vec<Source>,
pub caveat: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_all_returns_every_variant() {
assert_eq!(Source::all().len(), 18);
}
#[test]
fn exit_code_graduated() {
assert_eq!(Saturation::Open.exit_code(), 0);
assert_eq!(Saturation::Crowded.exit_code(), 1);
assert_eq!(Saturation::Saturated.exit_code(), 2);
}
}