use axoasset::SourceFile;
use cargo_dist_schema::DistManifest;
use chrono::DateTime;
use serde::Serialize;
use crate::config::ArtifactsConfig;
use crate::data::{cargo_dist, github::GithubRelease, GithubRepo};
use crate::errors::*;
use super::artifacts::ReleaseArtifacts;
use super::axodotdev::AxoRelease;
#[allow(clippy::large_enum_variant)]
#[derive(Serialize, Debug, Clone)]
pub enum ReleaseSource {
Github(GithubRelease),
Axodotdev(AxoRelease),
CurrentState(CurrentStateRelease),
}
#[derive(Serialize, Debug, Clone)]
pub struct CurrentStateRelease {
pub version: Option<String>,
pub date: Option<String>,
pub prerelease: bool,
}
impl ReleaseSource {
pub fn version_tag(&self) -> &str {
match self {
ReleaseSource::Github(src) => &src.tag_name,
ReleaseSource::Axodotdev(src) => &src.tag_name,
ReleaseSource::CurrentState(src) => src.version.as_deref().unwrap_or("current"),
}
}
pub fn is_prerelease(&self) -> bool {
match self {
ReleaseSource::Github(src) => src.prerelease,
ReleaseSource::Axodotdev(src) => src.prerelease,
ReleaseSource::CurrentState(src) => src.prerelease,
}
}
pub fn date(&self) -> Option<&str> {
match self {
ReleaseSource::Github(src) => Some(src.published_at.as_str()),
ReleaseSource::Axodotdev(src) => Some(src.created_at.as_str()),
ReleaseSource::CurrentState(src) => src.date.as_deref(),
}
}
pub fn formatted_date(&self) -> Option<String> {
self.date().map(|date| {
if let Ok(parsed_date) = DateTime::parse_from_rfc3339(date) {
parsed_date.format("%b %e %Y at %R UTC").to_string()
} else {
date.to_owned()
}
})
}
pub fn name(&self) -> Option<&str> {
match self {
ReleaseSource::Github(src) => src.name.as_deref(),
ReleaseSource::Axodotdev(src) => Some(src.name.as_str()),
ReleaseSource::CurrentState(_src) => None,
}
}
pub(crate) fn body(&self) -> Option<&str> {
match self {
ReleaseSource::Github(src) => src.body.as_deref(),
ReleaseSource::Axodotdev(src) => Some(src.body.as_str()),
ReleaseSource::CurrentState(_src) => None,
}
}
pub fn is_current_state(&self) -> bool {
matches!(self, ReleaseSource::CurrentState(_))
}
}
#[derive(Serialize, Clone, Debug)]
pub struct Release {
#[serde(skip_serializing)]
pub manifest: Option<DistManifest>,
#[serde(skip_serializing)]
pub source: ReleaseSource,
pub artifacts: ReleaseArtifacts,
}
impl Release {
pub async fn new(
source: ReleaseSource,
repo: Option<&GithubRepo>,
artifacts_config: Option<&ArtifactsConfig>,
) -> Result<Self> {
let Some(artifacts_config) = artifacts_config else {
return Ok(Self {
manifest: None,
source,
artifacts: ReleaseArtifacts::new(None),
});
};
let manifest = if let (ReleaseSource::Github(gh_release), Some(repo)) = (&source, repo) {
if artifacts_config.cargo_dist {
Self::fetch_manifest_github(gh_release, repo).await?
} else {
None
}
} else if let ReleaseSource::Axodotdev(axo_release) = &source {
if artifacts_config.cargo_dist {
Self::fetch_manifest_axodotdev(axo_release).await?
} else {
None
}
} else {
None
};
let mut artifacts = ReleaseArtifacts::new(None);
if let ReleaseSource::Github(gh_release) = &source {
artifacts.add_github(gh_release);
}
if let ReleaseSource::Axodotdev(axo_release) = &source {
artifacts.add_axodotdev(axo_release);
}
if let Some(manifest) = &manifest {
artifacts.add_cargo_dist(manifest);
}
artifacts.add_package_managers(artifacts_config);
artifacts.add_inference();
artifacts.select_installers(artifacts_config);
Ok(Self {
manifest,
source,
artifacts,
})
}
pub fn has_installers(&self) -> bool {
!self.artifacts.installers_by_target().is_empty()
}
async fn fetch_manifest_github(
gh_release: &GithubRelease,
repo: &GithubRepo,
) -> Result<Option<DistManifest>> {
let mut encoded_tag = String::new();
url_escape::encode_component_to_string(&gh_release.tag_name, &mut encoded_tag);
if gh_release.has_dist_manifest() {
let request = octolotl::request::ReleaseAsset::new(
&repo.owner,
&repo.name,
&encoded_tag,
cargo_dist::MANIFEST_FILENAME,
);
let response = octolotl::Request::send(&request, true)
.await?
.error_for_status()?;
Ok(Self::parse_response(response, &gh_release.tag_name).await?)
} else {
Ok(None)
}
}
async fn fetch_manifest_axodotdev(axo_release: &AxoRelease) -> Result<Option<DistManifest>> {
let mut encoded_tag = String::new();
url_escape::encode_component_to_string(&axo_release.tag_name, &mut encoded_tag);
if axo_release.has_dist_manifest() {
let response = reqwest::get(axo_release.asset_url("dist-manifest.json").unwrap())
.await?
.error_for_status()?;
Ok(Self::parse_response(response, &axo_release.tag_name).await?)
} else {
Ok(None)
}
}
async fn parse_response(
response: reqwest::Response,
tag: &str,
) -> Result<Option<DistManifest>> {
let res = response.text().await?;
let src = SourceFile::new("dist-manifest.json", res);
Ok(match src.deserialize_json::<DistManifest>() {
Ok(manifest) => Some(manifest),
Err(e) => {
let info = cargo_dist_schema::check_version(src.contents());
if let Some(info) = info {
if info.format.unsupported() {
} else {
let schema_version = info.version.to_string();
let parser_version = cargo_dist_schema::SELF_VERSION.to_owned();
let tag = tag.to_owned();
let err = OrandaError::CargoDistManifestPartial {
schema_version,
parser_version,
tag,
details: e,
};
let report = miette::Report::new(err);
eprintln!("{report:?}");
}
} else {
let tag = tag.to_owned();
let err = OrandaError::CargoDistManifestMalformed { tag, details: e };
let report = miette::Report::new(err);
eprintln!("{report:?}");
}
None
}
})
}
}