use chrono::{DateTime, Utc};
use compact_str::CompactString;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Release {
pub name: String,
pub tag_name: String,
pub description: Option<String>,
pub created_at: DateTime<Utc>,
pub released_at: DateTime<Utc>,
pub upcoming_release: bool,
pub author: Author,
pub commit: Commit,
pub milestones: Option<Vec<Milestone>>,
pub commit_path: String,
pub tag_path: String,
pub assets: ReleaseAssets,
pub evidences: Vec<ReleaseEvidence>,
pub _links: ReleaseLinks,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AuthorId(u64);
impl AuthorId {
pub const fn new(id: u64) -> Self {
Self(id)
}
pub const fn into_u64(self) -> u64 {
self.0
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Author {
pub id: AuthorId,
pub name: String,
pub username: String,
pub state: String,
pub avatar_url: String,
pub web_url: String,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Commit {
pub id: String,
pub short_id: String,
pub title: String,
pub created_at: DateTime<Utc>,
pub parent_ids: Vec<String>,
pub message: String,
pub author_name: String,
pub author_email: String,
pub authored_date: DateTime<Utc>,
pub committer_name: String,
pub committer_email: String,
pub committed_date: DateTime<Utc>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Milestone {
// TODO
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReleaseAssets {
pub count: u64,
pub sources: Vec<ReleaseSource>,
pub links: Vec<ReleaseLink>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReleaseSource {
pub format: String,
pub url: String,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReleaseEvidence {
// TODO
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReleaseLinks {
closed_issues_url: String,
closed_merge_requests_url: String,
edit_url: Option<String>,
merged_merge_requests_url: String,
opened_issues_url: String,
opened_merge_requests_url: String,
#[cfg_attr(feature = "serde", serde(rename = "self"))]
this: String,
}
/// Criteria used to order releases
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ReleaseOrder {
ReleasedAt,
CreatedAt,
}
impl ReleaseOrder {
pub fn as_str(self) -> &'static str {
match self {
Self::ReleasedAt => "released_at",
Self::CreatedAt => "created_at",
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReleaseLinkId(u64);
impl ReleaseLinkId {
pub const fn new(id: u64) -> Self {
Self(id)
}
pub const fn into_u64(self) -> u64 {
self.0
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReleaseLink {
pub id: ReleaseLinkId,
pub name: String,
pub url: String,
pub direct_asset_url: String,
pub link_type: ReleaseLinkType,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ReleaseLinkType {
#[cfg_attr(feature = "serde", serde(rename = "other"))]
Other,
#[cfg_attr(feature = "serde", serde(rename = "runbook"))]
Runbook,
#[cfg_attr(feature = "serde", serde(rename = "image"))]
Image,
#[cfg_attr(feature = "serde", serde(rename = "package"))]
Package,
}
impl ReleaseLinkType {
pub fn as_str(self) -> &'static str {
match self {
Self::Other => "other",
Self::Runbook => "runbook",
Self::Image => "image",
Self::Package => "package",
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InputReleaseAssets<Links = Vec<InputReleaseLink<CompactString>>> {
pub links: Links,
}
pub type InputReleaseAssetsView<'req, Str = CompactString> = InputReleaseAssets<&'req [InputReleaseLink<Str>]>;
impl<Links> InputReleaseAssets<Links> {
pub fn as_view<Str>(&self) -> InputReleaseAssetsView<'_, Str>
where
Links: AsRef<[InputReleaseLink<Str>]>,
{
InputReleaseAssetsView {
links: self.links.as_ref(),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InputReleaseLink<Str = CompactString> {
pub name: Str,
pub url: Str,
pub direct_asset_path: Option<Str>,
pub link_type: ReleaseLinkType,
}
pub type InputReleaseLinkView<'req> = InputReleaseLink<&'req str>;
impl<Str: AsRef<str>> InputReleaseLink<Str> {
pub fn as_view(&self) -> InputReleaseLinkView<'_> {
InputReleaseLinkView {
name: self.name.as_ref(),
url: self.url.as_ref(),
direct_asset_path: self.direct_asset_path.as_ref().map(|s| s.as_ref()),
link_type: self.link_type,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use chrono::{TimeZone, Utc};
#[cfg_attr(feature = "serde", test)]
#[allow(deprecated)]
fn read_release() {
let raw = r#"{"name": "v0.12.5","tag_name": "v0.12.5","description": null,"created_at": "2023-03-25T23:03:17.165Z","released_at": "2023-03-25T23:03:17.165Z","upcoming_release": false,"author": {"id": 743516,"username": "demurgos","name": "Charles Samborski","state": "active","avatar_url": "https://secure.gravatar.com/avatar/4ccbb457b6d50bb79ec32b66c2f4e301?s=80&d=identicon","web_url": "https://gitlab.com/demurgos"},"commit": {"id": "e8cfd320ee2b8dcee22b0bc0ceaf52d7dd667c2d","short_id": "e8cfd320","created_at": "2023-03-17T00:39:35.000+01:00","parent_ids": ["c8528d12e05c9ee803547eb556cb5394ac3d1abc"],"title": "[bin] Add support for precompiled binary","message": "[bin] Add support for precompiled binary\n\nThis commit adds support for precompiled binaries built in GitLab CI.\n","author_name": "Charles Samborski","author_email": "demurgos@demurgos.net","authored_date": "2023-03-16T23:56:54.000+01:00","committer_name": "Charles Samborski","committer_email": "demurgos@demurgos.net","committed_date": "2023-03-17T00:39:35.000+01:00","trailers": {},"web_url": "https://gitlab.com/eternaltwin/eternaltwin/-/commit/e8cfd320ee2b8dcee22b0bc0ceaf52d7dd667c2d"},"commit_path": "/eternaltwin/eternaltwin/-/commit/e8cfd320ee2b8dcee22b0bc0ceaf52d7dd667c2d","tag_path": "/eternaltwin/eternaltwin/-/tags/v0.12.5","assets": {"count": 5,"sources": [{"format": "zip","url": "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.zip"},{"format": "tar.gz","url": "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.tar.gz"},{"format": "tar.bz2","url": "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.tar.bz2"},{"format": "tar","url": "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.tar"}],"links": [{"id": 1492034,"name": "eternaltwin-x86_64-apple-darwin","url": "https://gitlab.com/eternaltwin/eternaltwin/-/packages/13511182","direct_asset_url": "https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5/downloads/eternaltwin-x86_64-apple-darwin","link_type": "package"}]},"evidences": [{"sha": "f9021712d926f78fc9272ac24733492facd5fb4353ed","filepath": "https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5/evidences/4460894.json","collected_at": "2023-03-25T23:03:17.328Z"}],"_links": {"closed_issues_url": "https://gitlab.com/eternaltwin/eternaltwin/-/issues?release_tag=v0.12.5&scope=all&state=closed","closed_merge_requests_url": "https://gitlab.com/eternaltwin/eternaltwin/-/merge_requests?release_tag=v0.12.5&scope=all&state=closed","edit_url": "https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5/edit","merged_merge_requests_url": "https://gitlab.com/eternaltwin/eternaltwin/-/merge_requests?release_tag=v0.12.5&scope=all&state=merged","opened_issues_url": "https://gitlab.com/eternaltwin/eternaltwin/-/issues?release_tag=v0.12.5&scope=all&state=opened","opened_merge_requests_url": "https://gitlab.com/eternaltwin/eternaltwin/-/merge_requests?release_tag=v0.12.5&scope=all&state=opened","self": "https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5"}}"#;
let actual: Release = serde_json::from_str(raw).unwrap();
let expected = Release {
name: "v0.12.5".to_string(),
tag_name: "v0.12.5".to_string(),
description: None,
created_at: Utc.ymd(2023, 3, 25).and_hms_milli(23, 3, 17, 165),
released_at: Utc.ymd(2023, 3, 25).and_hms_milli(23, 3, 17, 165),
upcoming_release: false,
author: Author {
id: AuthorId::new(743516),
name: "Charles Samborski".to_string(),
username: "demurgos".to_string(),
state: "active".to_string(),
avatar_url: "https://secure.gravatar.com/avatar/4ccbb457b6d50bb79ec32b66c2f4e301?s=80&d=identicon".to_string(),
web_url: "https://gitlab.com/demurgos".to_string(),
},
commit: Commit {
id: "e8cfd320ee2b8dcee22b0bc0ceaf52d7dd667c2d".to_string(),
short_id: "e8cfd320".to_string(),
title: "[bin] Add support for precompiled binary".to_string(),
created_at: Utc.ymd(2023, 3, 16).and_hms(23, 39, 35),
parent_ids: vec![
"c8528d12e05c9ee803547eb556cb5394ac3d1abc".to_string(),
],
message: "[bin] Add support for precompiled binary\n\nThis commit adds support for precompiled binaries built in GitLab CI.\n".to_string(),
author_name: "Charles Samborski".to_string(),
author_email: "demurgos@demurgos.net".to_string(),
authored_date: Utc.ymd(2023, 3, 16).and_hms(22, 56, 54),
committer_name: "Charles Samborski".to_string(),
committer_email: "demurgos@demurgos.net".to_string(),
committed_date: Utc.ymd(2023, 3, 16).and_hms(23, 39, 35),
},
milestones: None,
commit_path: "/eternaltwin/eternaltwin/-/commit/e8cfd320ee2b8dcee22b0bc0ceaf52d7dd667c2d".to_string(),
tag_path: "/eternaltwin/eternaltwin/-/tags/v0.12.5".to_string(),
assets: ReleaseAssets {
count: 5,
sources: vec![
ReleaseSource {
format: "zip".to_string(),
url: "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.zip".to_string(),
},
ReleaseSource {
format: "tar.gz".to_string(),
url: "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.tar.gz".to_string(),
},
ReleaseSource {
format: "tar.bz2".to_string(),
url: "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.tar.bz2".to_string(),
},
ReleaseSource {
format: "tar".to_string(),
url: "https://gitlab.com/eternaltwin/eternaltwin/-/archive/v0.12.5/eternaltwin-v0.12.5.tar".to_string(),
},
],
links: vec![
ReleaseLink {
id: ReleaseLinkId::new(1492034),
name: "eternaltwin-x86_64-apple-darwin".to_string(),
url: "https://gitlab.com/eternaltwin/eternaltwin/-/packages/13511182".to_string(),
direct_asset_url: "https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5/downloads/eternaltwin-x86_64-apple-darwin".to_string(),
link_type: ReleaseLinkType::Package,
}
],
},
evidences: vec![
ReleaseEvidence {},
],
_links: ReleaseLinks {
closed_issues_url: "https://gitlab.com/eternaltwin/eternaltwin/-/issues?release_tag=v0.12.5&scope=all&state=closed".to_string(),
closed_merge_requests_url: "https://gitlab.com/eternaltwin/eternaltwin/-/merge_requests?release_tag=v0.12.5&scope=all&state=closed".to_string(),
edit_url: Some("https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5/edit".to_string()),
merged_merge_requests_url: "https://gitlab.com/eternaltwin/eternaltwin/-/merge_requests?release_tag=v0.12.5&scope=all&state=merged".to_string(),
opened_issues_url: "https://gitlab.com/eternaltwin/eternaltwin/-/issues?release_tag=v0.12.5&scope=all&state=opened".to_string(),
opened_merge_requests_url: "https://gitlab.com/eternaltwin/eternaltwin/-/merge_requests?release_tag=v0.12.5&scope=all&state=opened".to_string(),
this: "https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5".to_string(),
},
};
assert_eq!(actual, expected);
}
#[cfg_attr(feature = "serde", test)]
#[allow(deprecated)]
fn read_release_link() {
let raw = r#"{"id":1492019,"name":"eternaltwin-x86_64-unknown-linux-gnu","url":"https://gitlab.com/eternaltwin/eternaltwin/-/packages/13511234","direct_asset_url":"https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5/downloads/eternaltwin-x86_64-unknown-linux-gnu","link_type":"package"}"#;
let actual: ReleaseLink = serde_json::from_str(raw).unwrap();
let expected = ReleaseLink {
id: ReleaseLinkId::new(1492019),
name: "eternaltwin-x86_64-unknown-linux-gnu".to_string(),
url: "https://gitlab.com/eternaltwin/eternaltwin/-/packages/13511234".to_string(),
direct_asset_url:
"https://gitlab.com/eternaltwin/eternaltwin/-/releases/v0.12.5/downloads/eternaltwin-x86_64-unknown-linux-gnu"
.to_string(),
link_type: ReleaseLinkType::Package,
};
assert_eq!(actual, expected);
}
#[cfg_attr(feature = "serde", test)]
#[allow(deprecated)]
fn read_xml2_release_list() {
let raw = include_str!("../../test-resources/get-project-release-list/libxml2/output.json");
let actual: Vec<Release> = serde_json::from_str(raw).unwrap();
assert_eq!(actual.len(), 20);
}
}