use serde::{Serialize, Deserialize};
use tracing::debug;
mod http;
mod error;
mod target;
mod package_id;
pub use http::HttpAgent;
pub use error::{Error, Result};
pub use target::{Target, package_target};
pub use package_id::{PackageId, GroupName, PackageName, Registry, WithVersion, MaybeVersion};
use semver::Version;
pub const INDEX_HOST: &str = "https://packages.fluvio.io/";
pub const INDEX_LOCATION: &str = "https://packages.fluvio.io/v1/";
pub const INDEX_CLIENT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Serialize, Deserialize)]
pub struct IndexMetadata {
pub minimum_client_version: Version,
}
impl IndexMetadata {
pub fn update_required(&self) -> bool {
let client_version = Version::parse(INDEX_CLIENT_VERSION).unwrap();
let required_version = &self.minimum_client_version;
*required_version > client_version
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluvioIndex {
#[serde(alias = "index")]
pub metadata: IndexMetadata,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Package {
pub name: PackageName,
pub group: GroupName,
pub kind: PackageKind,
pub author: Option<String>,
pub description: Option<String>,
pub repository: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
releases: Vec<Release>,
}
impl Package {
pub fn new_binary<S1, S2, S3, V>(id: &PackageId<V>, author: S1, desc: S2, repo: S3) -> Self
where
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
{
let author = author.into();
let description = desc.into();
let repository = repo.into();
Package {
name: id.name().clone(),
group: id.group().clone(),
kind: PackageKind::Binary,
author: Some(author),
description: Some(description),
repository: Some(repository),
releases: vec![],
}
}
pub fn latest_release(&self) -> Result<&Release> {
debug!(releases = ?&self.releases, "Finding latest release");
self.releases
.last()
.ok_or_else(|| Error::NoReleases(self.package_id().to_string()))
}
pub fn latest_release_for_target(&self, target: Target, prerelease: bool) -> Result<&Release> {
self.releases
.iter()
.rev()
.find(|it| it.targets.contains(&target) && (prerelease || !it.version.is_prerelease()))
.ok_or(Error::MissingTarget(target))
}
fn package_id(&self) -> PackageId<MaybeVersion> {
PackageId::new_unversioned(self.name.clone(), self.group.clone())
}
pub fn add_release(&mut self, version: Version, target: Target) -> Result<()> {
let maybe_release = self
.releases
.iter_mut()
.find(|it| version_exactly_eq(&it.version, &version));
match maybe_release {
Some(release) => release.add_target(target),
None => {
let release = Release::new(version, target);
self.releases.push(release);
self.releases.sort_by(|a, b| a.version.cmp(&b.version));
}
}
Ok(())
}
pub fn releases_for_target(&self, target: Target) -> Vec<&Release> {
self.releases
.iter()
.filter(|it| it.targets.contains(&target))
.collect()
}
}
fn version_exactly_eq(a: &Version, b: &Version) -> bool {
a.eq(b) && a.build.eq(&b.build)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PackageKind {
#[serde(rename = "bin")]
Binary,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Release {
pub version: Version,
pub yanked: bool,
targets: Vec<Target>,
}
impl Release {
pub fn new(version: Version, target: Target) -> Self {
Self {
version,
yanked: false,
targets: vec![target],
}
}
pub fn add_target(&mut self, target: Target) {
if !self.target_exists(target) {
self.targets.push(target);
}
}
pub fn target_exists(&self, target: Target) -> bool {
self.targets.iter().any(|it| it == &target)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_package() -> Package {
Package {
name: "my-package".parse().unwrap(),
group: "my-group".parse().unwrap(),
kind: PackageKind::Binary,
author: None,
description: None,
repository: None,
releases: vec![
Release {
version: Version::parse("0.1.0-alpha.1").unwrap(),
yanked: false,
targets: vec![Target::X86_64AppleDarwin],
},
Release {
version: Version::parse("0.1.0").unwrap(),
yanked: false,
targets: vec![Target::X86_64AppleDarwin],
},
Release {
version: Version::parse("0.2.0-alpha.1").unwrap(),
yanked: false,
targets: vec![Target::X86_64AppleDarwin],
},
Release {
version: Version::parse("0.2.0-alpha.2").unwrap(),
yanked: false,
targets: vec![Target::X86_64AppleDarwin],
},
],
}
}
#[test]
fn test_serialize_package() {
let id: PackageId<MaybeVersion> = "fluvio/fluvio".parse().unwrap();
let package = Package::new_binary(&id, "Bob", "A package", "https://github.com");
let stringified = serde_json::to_string(&package).unwrap();
assert_eq!(
stringified,
r#"{"name":"fluvio","group":"fluvio","kind":"bin","author":"Bob","description":"A package","repository":"https://github.com"}"#
)
}
#[test]
fn test_get_latest_prerelease() {
let package = test_package();
let release = package
.latest_release_for_target(Target::X86_64AppleDarwin, true)
.unwrap();
assert_eq!(release.version, Version::parse("0.2.0-alpha.2").unwrap());
}
#[test]
fn test_get_latest_release() {
let package = test_package();
let release = package
.latest_release_for_target(Target::X86_64AppleDarwin, false)
.unwrap();
assert_eq!(release.version, Version::parse("0.1.0").unwrap());
}
}