use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use chrono::{DateTime, NaiveDate, Utc};
use serde::de::{DeserializeOwned, Error};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{self, Value};
impl_id!(UserId, "Type-safe user ID.");
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserState {
#[serde(rename = "active")]
Active,
#[serde(rename = "blocked")]
Blocked,
#[serde(rename = "ldap_blocked")]
LdapBlocked,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserBasic {
pub username: String,
pub name: String,
pub id: UserId,
pub state: UserState,
pub avatar_url: Option<String>,
pub web_url: String,
}
pub trait UserResult: DeserializeOwned {}
impl<T: DeserializeOwned + Into<UserBasic>> UserResult for T {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct User {
pub username: String,
pub name: String,
pub id: UserId,
pub state: UserState,
pub avatar_url: Option<String>,
pub web_url: String,
pub created_at: Option<DateTime<Utc>>,
pub is_admin: Option<bool>,
pub highest_role: Option<AccessLevel>,
pub bio: Option<String>,
pub private_profile: Option<bool>,
pub location: Option<String>,
pub public_email: Option<String>,
pub skype: String,
pub linkedin: String,
pub twitter: String,
pub website_url: String,
pub organization: Option<String>,
}
impl From<User> for UserBasic {
fn from(user: User) -> Self {
UserBasic {
username: user.username,
name: user.name,
id: user.id,
state: user.state,
avatar_url: user.avatar_url,
web_url: user.web_url,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Identity {
pub provider: String,
pub extern_uid: String,
}
impl_id!(ThemeId, "Type-safe theme ID.");
impl_id!(ColorSchemeId, "Type-safe color scheme ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserPublic {
pub username: String,
pub name: String,
pub id: UserId,
pub state: UserState,
pub avatar_url: Option<String>,
pub web_url: String,
pub created_at: Option<DateTime<Utc>>,
pub is_admin: Option<bool>,
pub highest_role: Option<AccessLevel>,
pub bio: Option<String>,
pub private_profile: Option<bool>,
pub location: Option<String>,
pub public_email: Option<String>,
pub skype: String,
pub linkedin: String,
pub twitter: String,
pub website_url: String,
pub organization: Option<String>,
pub last_sign_in_at: Option<DateTime<Utc>>,
pub last_activity_on: Option<NaiveDate>,
pub confirmed_at: Option<DateTime<Utc>>,
pub email: String,
pub theme_id: Option<ThemeId>,
pub color_scheme_id: ColorSchemeId,
pub projects_limit: u64,
pub current_sign_in_at: Option<DateTime<Utc>>,
pub identities: Vec<Identity>,
pub can_create_group: bool,
pub can_create_project: bool,
pub two_factor_enabled: bool,
pub external: bool,
}
impl From<UserPublic> for UserBasic {
fn from(user: UserPublic) -> Self {
UserBasic {
username: user.username,
name: user.name,
id: user.id,
state: user.state,
avatar_url: user.avatar_url,
web_url: user.web_url,
}
}
}
impl From<UserPublic> for User {
fn from(user: UserPublic) -> Self {
User {
username: user.username,
name: user.name,
id: user.id,
state: user.state,
avatar_url: user.avatar_url,
web_url: user.web_url,
created_at: user.created_at,
is_admin: user.is_admin,
highest_role: user.highest_role,
bio: user.bio,
private_profile: user.private_profile,
location: user.location,
public_email: user.public_email,
skype: user.skype,
linkedin: user.linkedin,
twitter: user.twitter,
website_url: user.website_url,
organization: user.organization,
}
}
}
impl_id!(EmailId, "Type-safe email ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Email {
pub id: EmailId,
pub email: String,
}
impl_id!(HookId, "Type-safe hook ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Hook {
pub id: HookId,
pub url: String,
pub created_at: DateTime<Utc>,
pub push_events: bool,
pub tag_push_events: bool,
pub enable_ssl_verification: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProjectHook {
pub id: HookId,
pub url: String,
pub created_at: DateTime<Utc>,
pub project_id: ProjectId,
pub push_events: bool,
pub push_events_branch_filter: Option<String>,
pub tag_push_events: bool,
pub issues_events: bool,
pub confidential_issues_events: Option<bool>,
pub merge_requests_events: bool,
pub note_events: bool,
pub confidential_note_events: Option<bool>,
pub repository_update_events: bool,
pub enable_ssl_verification: bool,
pub job_events: bool,
pub pipeline_events: bool,
pub wiki_page_events: bool,
pub token: Option<String>,
}
impl From<ProjectHook> for Hook {
fn from(hook: ProjectHook) -> Self {
Hook {
id: hook.id,
url: hook.url,
created_at: hook.created_at,
push_events: hook.push_events,
tag_push_events: hook.tag_push_events,
enable_ssl_verification: hook.enable_ssl_verification,
}
}
}
impl_id!(ProjectId, "Type-safe project ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BasicProjectDetails {
pub id: ProjectId,
pub name: String,
pub name_with_namespace: String,
pub path: String,
pub path_with_namespace: String,
pub http_url_to_repo: String,
pub web_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum VisibilityLevel {
#[serde(rename = "public")]
Public,
#[serde(rename = "internal")]
Internal,
#[serde(rename = "private")]
Private,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FeatureVisibilityLevel {
#[serde(rename = "disabled")]
Disabled,
#[serde(rename = "private")]
Private,
#[serde(rename = "enabled")]
Enabled,
#[serde(rename = "public")]
Public,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SharedGroup {
pub group_id: GroupId,
pub group_name: String,
pub group_access_level: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct MemberAccess {
pub access_level: u64,
pub notification_level: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct Permissions {
pub project_access: Option<MemberAccess>,
pub group_access: Option<MemberAccess>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProjectNamespaceAvatar {
pub url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct ProjectLinks {
#[serde(rename = "self")]
self_: String,
issues: Option<String>,
merge_requests: Option<String>,
repo_branches: String,
labels: String,
events: String,
members: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Project {
pub id: ProjectId,
pub description: Option<String>,
pub default_branch: Option<String>,
pub tag_list: Vec<String>,
pub archived: bool,
pub empty_repo: bool,
pub visibility: VisibilityLevel,
pub ssh_url_to_repo: String,
pub http_url_to_repo: String,
pub web_url: String,
pub readme_url: Option<String>,
pub owner: Option<UserBasic>,
pub name: String,
pub name_with_namespace: String,
pub path: String,
pub path_with_namespace: String,
pub container_registry_enabled: Option<bool>,
pub created_at: DateTime<Utc>,
pub last_activity_at: DateTime<Utc>,
pub shared_runners_enabled: bool,
pub lfs_enabled: bool,
pub creator_id: UserId,
pub namespace: Namespace,
pub forked_from_project: Option<BasicProjectDetails>,
pub avatar_url: Option<String>,
pub ci_config_path: Option<String>,
pub import_error: Option<String>,
pub star_count: u64,
pub forks_count: u64,
pub open_issues_count: Option<u64>,
pub runners_token: Option<String>,
pub public_jobs: bool,
pub shared_with_groups: Vec<SharedGroup>,
pub only_allow_merge_if_pipeline_succeeds: Option<bool>,
pub only_allow_merge_if_all_discussions_are_resolved: Option<bool>,
pub remove_source_branch_after_merge: Option<bool>,
pub printing_merge_request_link_enabled: Option<bool>,
pub request_access_enabled: bool,
pub resolve_outdated_diff_discussions: Option<bool>,
pub jobs_enabled: bool,
pub issues_enabled: bool,
pub merge_requests_enabled: bool,
pub snippets_enabled: bool,
pub wiki_enabled: bool,
pub builds_access_level: FeatureVisibilityLevel,
pub issues_access_level: FeatureVisibilityLevel,
pub merge_requests_access_level: FeatureVisibilityLevel,
pub repository_access_level: FeatureVisibilityLevel,
pub snippets_access_level: FeatureVisibilityLevel,
pub wiki_access_level: FeatureVisibilityLevel,
pub merge_method: Option<String>,
pub statistics: Option<ProjectStatistics>,
pub permissions: Option<Permissions>,
_links: Option<ProjectLinks>,
}
#[cfg(test)]
impl Project {
pub fn has_links(&self) -> bool {
self._links.is_some()
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct ProjectStatistics {
pub commit_count: u64,
pub storage_size: u64,
pub repository_size: u64,
pub lfs_objects_size: u64,
pub job_artifacts_size: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AccessLevel {
Anonymous,
Guest,
Reporter,
Developer,
Maintainer,
Owner,
Admin,
}
impl From<AccessLevel> for u64 {
fn from(access: AccessLevel) -> Self {
match access {
AccessLevel::Anonymous => 0,
AccessLevel::Guest => 10,
AccessLevel::Reporter => 20,
AccessLevel::Developer => 30,
AccessLevel::Maintainer => 40,
AccessLevel::Owner => 50,
AccessLevel::Admin => 60,
}
}
}
impl From<u64> for AccessLevel {
fn from(access: u64) -> Self {
if access >= 60 {
AccessLevel::Admin
} else if access >= 50 {
AccessLevel::Owner
} else if access >= 40 {
AccessLevel::Maintainer
} else if access >= 30 {
AccessLevel::Developer
} else if access >= 20 {
AccessLevel::Reporter
} else if access >= 10 {
AccessLevel::Guest
} else {
AccessLevel::Anonymous
}
}
}
impl AccessLevel {
pub fn as_str(&self) -> &str {
match self {
AccessLevel::Admin => "admin",
AccessLevel::Owner => "owner",
AccessLevel::Developer => "developer",
AccessLevel::Anonymous => "anonymous",
AccessLevel::Guest => "guest",
AccessLevel::Maintainer => "maintainer",
AccessLevel::Reporter => "reporter",
}
}
}
impl Display for AccessLevel {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", Into::<u64>::into(*self))
}
}
impl Serialize for AccessLevel {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
u64::from(*self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for AccessLevel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(<u64 as Deserialize>::deserialize(deserializer)?.into())
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Member {
pub username: String,
pub name: String,
pub id: UserId,
pub state: UserState,
pub avatar_url: Option<String>,
pub web_url: String,
pub access_level: u64,
pub expires_at: Option<NaiveDate>,
}
impl From<Member> for UserBasic {
fn from(member: Member) -> Self {
UserBasic {
username: member.username,
name: member.name,
id: member.id,
state: member.state,
avatar_url: member.avatar_url,
web_url: member.web_url,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AccessRequester {
pub username: String,
pub name: String,
pub id: UserId,
pub state: UserState,
pub avatar_url: Option<String>,
pub web_url: String,
pub requested_at: DateTime<Utc>,
}
impl From<AccessRequester> for UserBasic {
fn from(member: AccessRequester) -> Self {
UserBasic {
username: member.username,
name: member.name,
id: member.id,
state: member.state,
avatar_url: member.avatar_url,
web_url: member.web_url,
}
}
}
impl_id!(GroupId, "Type-safe group ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Group {
pub id: GroupId,
pub name: String,
pub path: String,
pub description: Option<String>,
pub visibility: VisibilityLevel,
pub lfs_enabled: bool,
pub avatar_url: Option<String>,
pub web_url: String,
pub request_access_enabled: bool,
pub full_name: String,
pub full_path: String,
pub parent_id: Option<GroupId>,
pub statistics: Option<GroupStatistics>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct GroupStatistics {
pub storage_size: u64,
pub repository_size: u64,
pub lfs_objects_size: u64,
pub job_artifacts_size: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GroupDetail {
pub id: GroupId,
pub name: String,
pub path: String,
pub description: Option<String>,
pub visibility: VisibilityLevel,
pub lfs_enabled: bool,
pub avatar_url: Option<String>,
pub web_url: String,
pub projects: Vec<Project>,
pub shared_projects: Vec<Project>,
pub request_access_enabled: bool,
pub full_name: String,
pub full_path: String,
pub parent_id: Option<GroupId>,
pub statistics: Option<GroupStatistics>,
}
impl From<GroupDetail> for Group {
fn from(detail: GroupDetail) -> Self {
Group {
id: detail.id,
name: detail.name,
path: detail.path,
description: detail.description,
visibility: detail.visibility,
lfs_enabled: detail.lfs_enabled,
avatar_url: detail.avatar_url,
web_url: detail.web_url,
request_access_enabled: detail.request_access_enabled,
full_name: detail.full_name,
full_path: detail.full_path,
parent_id: detail.parent_id,
statistics: detail.statistics,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RepoBranch {
pub name: String,
pub commit: Option<RepoCommit>,
pub merged: Option<bool>,
pub protected: Option<bool>,
pub developers_can_push: Option<bool>,
pub developers_can_merge: Option<bool>,
pub can_push: Option<bool>,
pub default: Option<bool>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct PRBAccessLevel {
access_level: u64,
access_level_description: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ProtectedRepoBranch {
pub name: String,
pub push_access_levels: Vec<PRBAccessLevel>,
pub merge_access_levels: Vec<PRBAccessLevel>,
pub code_owner_approval_required: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
pub struct ObjectId(String);
impl ObjectId {
pub fn new<O: ToString>(oid: O) -> Self {
ObjectId(oid.to_string())
}
pub fn value(&self) -> &String {
&self.0
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObjectType {
#[serde(rename = "tree")]
Tree,
#[serde(rename = "blob")]
Blob,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RepoTreeObject {
pub id: ObjectId,
pub name: String,
#[serde(rename = "type")]
pub type_: ObjectType,
pub path: String,
pub mode: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RepoCommit {
pub id: ObjectId,
pub short_id: ObjectId,
pub title: String,
pub parent_ids: Vec<ObjectId>,
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>,
pub created_at: DateTime<Utc>,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct RepoCommitStats {
pub additions: u64,
pub deletions: u64,
pub total: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RepoCommitDetail {
pub id: ObjectId,
pub short_id: ObjectId,
pub title: String,
pub parent_ids: Vec<ObjectId>,
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>,
pub created_at: DateTime<Utc>,
pub message: String,
pub stats: Option<RepoCommitStats>,
pub last_pipeline: Option<PipelineBasic>,
pub project_id: ProjectId,
status: Value,
}
impl_id!(SnippetId, "Type-safe snippet ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProjectSnippet {
pub id: SnippetId,
pub title: String,
pub file_name: String,
pub author: UserBasic,
pub updated_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>,
pub web_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RepoDiff {
pub old_path: String,
pub new_path: String,
pub a_mode: String,
pub b_mode: String,
pub diff: String,
pub new_file: bool,
pub renamed_file: bool,
pub deleted_file: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct DiffRefs {
pub base_sha: Option<ObjectId>,
pub head_sha: Option<ObjectId>,
pub start_sha: Option<ObjectId>,
}
impl_id!(MilestoneId, "Type-safe milestone ID.");
impl_id!(
MilestoneInternalId,
"Type-safe milestone internal ID (internal to a project).",
);
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum MilestoneState {
#[serde(rename = "active")]
Active,
#[serde(rename = "closed")]
Closed,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Milestone {
pub id: MilestoneId,
pub iid: MilestoneInternalId,
pub project_id: Option<ProjectId>,
pub group_id: Option<GroupId>,
pub title: String,
pub description: Option<String>,
pub state: MilestoneState,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub due_date: Option<NaiveDate>,
pub start_date: Option<NaiveDate>,
}
impl Milestone {
pub fn new_for_project(project_id: ProjectId, title: String) -> Milestone {
Milestone {
id: MilestoneId::new(0),
iid: MilestoneInternalId::new(0),
project_id: Some(project_id),
group_id: None,
title,
description: None,
state: MilestoneState::Active,
created_at: Utc::now(),
updated_at: Utc::now(),
due_date: None,
start_date: None,
}
}
pub fn new_for_group(group_id: GroupId, title: String) -> Milestone {
Milestone {
id: MilestoneId::new(0),
iid: MilestoneInternalId::new(0),
project_id: None,
group_id: Some(group_id),
title,
description: None,
state: MilestoneState::Active,
created_at: Utc::now(),
updated_at: Utc::now(),
due_date: None,
start_date: None,
}
}
pub fn with_description(mut self, description: String) -> Milestone {
self.description = Some(description);
self
}
pub fn with_due_date(mut self, due_date: NaiveDate) -> Milestone {
self.due_date = Some(due_date);
self
}
pub fn with_start_date(mut self, start_date: NaiveDate) -> Milestone {
self.start_date = Some(start_date);
self
}
}
impl_id!(LabelId, "Type-safe label ID.");
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct LabelColor(String);
impl LabelColor {
pub fn from_rgb(r: u8, g: u8, b: u8) -> LabelColor {
LabelColor(format!("#{:02X}{:02X}{:02X}", r, g, b))
}
pub fn value(self) -> String {
self.0
}
}
impl FromStr for LabelColor {
type Err = ();
fn from_str(stdcolor: &str) -> Result<Self, Self::Err> {
let hex = match stdcolor {
"white" => "FFFFFF",
"silver" => "C0C0C0",
"gray" => "808080",
"black" => "000000",
"red" => "FF0000",
"maroon" => "800000",
"yellow" => "FFFF00",
"olive" => "808000",
"lime" => "00FF00",
"green" => "008000",
"aqua" => "00FFFF",
"teal" => "008080",
"blue" => "0000FF",
"navy" => "000080",
"fuchsia" => "FF00FF",
"purple" => "800080",
_ => "808080",
};
Ok(LabelColor(format!("#{}", hex)))
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Label {
pub id: LabelId,
pub name: String,
pub color: LabelColor,
pub description: Option<String>,
pub open_issues_count: Option<u64>,
pub closed_issues_count: Option<u64>,
pub open_merge_requests_count: Option<u64>,
pub subscribed: bool,
pub priority: Option<u64>,
}
impl Label {
pub fn new(name: String, color: LabelColor) -> Label {
Label {
id: LabelId::new(0),
name,
color,
description: None,
open_issues_count: None,
closed_issues_count: None,
open_merge_requests_count: None,
subscribed: false,
priority: None,
}
}
pub fn with_description(mut self, description: String) -> Label {
self.description = Some(description);
self
}
pub fn with_priority(mut self, priority: u64) -> Label {
self.priority = Some(priority);
self
}
}
impl_id!(IssueId, "Type-safe issue ID.");
impl_id!(
IssueInternalId,
"Type-safe issue internal ID (internal to a project).",
);
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueState {
#[serde(rename = "opened")]
Opened,
#[serde(rename = "closed")]
Closed,
#[serde(rename = "reopened")]
Reopened,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct IssueLinks {
#[serde(rename = "self")]
self_: String,
notes: String,
award_emoji: String,
project: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Issue {
pub id: IssueId,
pub iid: IssueInternalId,
pub project_id: ProjectId,
pub title: String,
pub description: Option<String>,
pub state: IssueState,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub closed_at: Option<DateTime<Utc>>,
pub closed_by: Option<UserBasic>,
pub labels: Vec<String>,
pub milestone: Option<Milestone>,
pub author: UserBasic,
pub assignee: Option<UserBasic>,
pub assignees: Option<Vec<UserBasic>>,
pub subscribed: Option<bool>,
pub time_stats: IssuableTimeStats,
pub user_notes_count: u64,
pub merge_requests_count: u64,
pub upvotes: u64,
pub downvotes: u64,
pub due_date: Option<NaiveDate>,
pub has_tasks: Option<bool>,
pub confidential: bool,
pub discussion_locked: Option<bool>,
pub web_url: String,
_links: Option<IssueLinks>,
}
impl Issue {
pub fn new(project_id: ProjectId, title: String, author: UserBasic) -> Issue {
Issue {
id: IssueId::new(0),
iid: IssueInternalId::new(0),
project_id,
title,
description: None,
state: IssueState::Opened,
created_at: Utc::now(),
updated_at: Utc::now(),
closed_at: None,
closed_by: None,
labels: Vec::new(),
milestone: None,
author,
assignee: None,
assignees: None,
subscribed: None,
time_stats: IssuableTimeStats {
time_estimate: 0,
total_time_spent: 0,
human_time_estimate: None,
human_total_time_spent: None,
},
user_notes_count: 0,
merge_requests_count: 0,
upvotes: 0,
downvotes: 0,
due_date: None,
has_tasks: None,
confidential: false,
discussion_locked: None,
web_url: "".into(),
_links: None,
}
}
pub fn with_iid(mut self, iid: IssueInternalId) -> Issue {
self.iid = iid;
self
}
pub fn with_description(mut self, description: String) -> Issue {
self.description = Some(description);
self
}
pub fn with_confidential(mut self, confidential: bool) -> Issue {
self.confidential = confidential;
self
}
pub fn with_assignees(mut self, assignees: Vec<UserBasic>) -> Issue {
self.assignees = Some(assignees);
self
}
pub fn with_milestone(mut self, milestone: Milestone) -> Issue {
self.milestone = Some(milestone);
self
}
pub fn with_labels(mut self, labels: Vec<String>) -> Issue {
self.labels = labels;
self
}
pub fn with_created_at(mut self, created_at: DateTime<Utc>) -> Issue {
self.created_at = created_at;
self
}
pub fn with_due_date(mut self, due_date: NaiveDate) -> Issue {
self.due_date = Some(due_date);
self
}
pub fn has_links(&self) -> bool {
self._links.is_some()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IssuableTimeStats {
pub time_estimate: u64,
pub total_time_spent: u64,
pub human_time_estimate: Option<String>,
pub human_total_time_spent: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExternalIssueId(u64);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExternalIssue {
pub id: ExternalIssueId,
pub title: String,
}
#[derive(Debug, Clone)]
pub enum IssueReference {
Internal(Box<Issue>),
External(ExternalIssue),
}
impl Serialize for IssueReference {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match *self {
IssueReference::Internal(ref issue) => issue.serialize(serializer),
IssueReference::External(ref issue) => issue.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for IssueReference {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let val = <Value as Deserialize>::deserialize(deserializer)?;
serde_json::from_value::<Issue>(val.clone())
.map(|issue| IssueReference::Internal(Box::new(issue)))
.or_else(|_| serde_json::from_value::<ExternalIssue>(val).map(IssueReference::External))
.map_err(|err| D::Error::custom(format!("invalid issue reference: {:?}", err)))
}
}
impl_id!(MergeRequestId, "Type-safe merge request ID.");
impl_id!(
MergeRequestInternalId,
"Type-safe merge request internal ID (internal to a project).",
);
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeStatus {
#[serde(rename = "unchecked")]
Unchecked,
#[serde(rename = "checking")]
Checking,
#[serde(rename = "can_be_merged")]
CanBeMerged,
#[serde(rename = "cannot_be_merged")]
CannotBeMerged,
#[serde(rename = "cannot_be_merged_recheck")]
CannotBeMergedRecheck,
#[serde(rename = "cannot_be_merged_rechecking")]
CannotBeMergedRechecking,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeRequestState {
#[serde(rename = "opened")]
Opened,
#[serde(rename = "closed")]
Closed,
#[serde(rename = "reopened")]
Reopened,
#[serde(rename = "merged")]
Merged,
#[serde(rename = "locked")]
Locked,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MergeRequestUser {
pub can_merge: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MergeRequest {
pub id: MergeRequestId,
pub iid: MergeRequestInternalId,
pub project_id: ProjectId,
pub title: String,
pub description: Option<String>,
pub state: MergeRequestState,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub merged_at: Option<DateTime<Utc>>,
pub closed_at: Option<DateTime<Utc>>,
pub merged_by: Option<UserBasic>,
pub closed_by: Option<UserBasic>,
pub target_branch: String,
pub source_branch: String,
pub upvotes: u64,
pub downvotes: u64,
pub author: UserBasic,
pub assignee: Option<UserBasic>,
pub assignees: Option<Vec<UserBasic>>,
pub source_project_id: ProjectId,
pub target_project_id: ProjectId,
pub labels: Vec<String>,
pub work_in_progress: bool,
pub allow_collaboration: Option<bool>,
pub allow_maintainer_to_push: Option<bool>,
pub milestone: Option<Milestone>,
pub squash: bool,
pub merge_when_pipeline_succeeds: bool,
pub merge_status: MergeStatus,
pub sha: Option<ObjectId>,
pub diff_refs: Option<DiffRefs>,
pub merge_error: Option<String>,
pub rebase_in_progress: Option<bool>,
pub merge_commit_sha: Option<ObjectId>,
pub squash_commit_sha: Option<ObjectId>,
pub subscribed: Option<bool>,
pub time_stats: IssuableTimeStats,
pub blocking_discussions_resolved: bool,
pub changes_count: Option<String>,
pub user_notes_count: u64,
pub discussion_locked: Option<bool>,
pub should_remove_source_branch: Option<bool>,
pub force_remove_source_branch: Option<bool>,
pub has_conflicts: bool,
pub user: Option<MergeRequestUser>,
pub web_url: String,
pub pipeline: Option<PipelineBasic>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MergeRequestChanges {
pub id: MergeRequestId,
pub iid: MergeRequestInternalId,
pub project_id: ProjectId,
pub title: String,
pub description: Option<String>,
pub state: MergeRequestState,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub merged_at: Option<DateTime<Utc>>,
pub closed_at: Option<DateTime<Utc>>,
pub merged_by: Option<UserBasic>,
pub closed_by: Option<UserBasic>,
pub target_branch: String,
pub source_branch: String,
pub upvotes: u64,
pub downvotes: u64,
pub author: UserBasic,
pub assignee: Option<UserBasic>,
pub assignees: Option<Vec<UserBasic>>,
pub source_project_id: ProjectId,
pub target_project_id: ProjectId,
pub labels: Vec<String>,
pub work_in_progress: bool,
pub allow_collaboration: Option<bool>,
pub allow_maintainer_to_push: Option<bool>,
pub milestone: Option<Milestone>,
pub squash: bool,
pub merge_when_pipeline_succeeds: bool,
pub merge_status: MergeStatus,
pub sha: Option<ObjectId>,
pub diff_refs: Option<DiffRefs>,
pub merge_error: Option<String>,
pub rebase_in_progress: Option<bool>,
pub merge_commit_sha: Option<ObjectId>,
pub squash_commit_sha: Option<ObjectId>,
pub subscribed: Option<bool>,
pub time_stats: IssuableTimeStats,
pub blocking_discussions_resolved: bool,
pub changes_count: Option<String>,
pub user_notes_count: u64,
pub discussion_locked: Option<bool>,
pub should_remove_source_branch: Option<bool>,
pub force_remove_source_branch: Option<bool>,
pub has_conflicts: bool,
pub user: MergeRequestUser,
pub web_url: String,
pub pipeline: Option<PipelineBasic>,
pub changes: Vec<RepoDiff>,
}
impl From<MergeRequestChanges> for MergeRequest {
fn from(mr: MergeRequestChanges) -> Self {
MergeRequest {
id: mr.id,
iid: mr.iid,
project_id: mr.project_id,
title: mr.title,
description: mr.description,
state: mr.state,
created_at: mr.created_at,
updated_at: mr.updated_at,
merged_at: mr.merged_at,
closed_at: mr.closed_at,
merged_by: mr.merged_by,
closed_by: mr.closed_by,
target_branch: mr.target_branch,
source_branch: mr.source_branch,
upvotes: mr.upvotes,
downvotes: mr.downvotes,
author: mr.author,
assignee: mr.assignee,
assignees: mr.assignees,
source_project_id: mr.source_project_id,
target_project_id: mr.target_project_id,
labels: mr.labels,
work_in_progress: mr.work_in_progress,
allow_collaboration: mr.allow_collaboration,
allow_maintainer_to_push: mr.allow_maintainer_to_push,
milestone: mr.milestone,
squash: mr.squash,
merge_when_pipeline_succeeds: mr.merge_when_pipeline_succeeds,
merge_status: mr.merge_status,
sha: mr.sha,
diff_refs: mr.diff_refs,
merge_error: mr.merge_error,
rebase_in_progress: mr.rebase_in_progress,
merge_commit_sha: mr.merge_commit_sha,
squash_commit_sha: mr.squash_commit_sha,
subscribed: mr.subscribed,
time_stats: mr.time_stats,
blocking_discussions_resolved: mr.blocking_discussions_resolved,
changes_count: mr.changes_count,
user_notes_count: mr.user_notes_count,
discussion_locked: mr.discussion_locked,
should_remove_source_branch: mr.should_remove_source_branch,
force_remove_source_branch: mr.force_remove_source_branch,
has_conflicts: mr.has_conflicts,
user: Some(mr.user),
web_url: mr.web_url,
pipeline: mr.pipeline,
}
}
}
impl_id!(SshKeyId, "Type-safe SSH key ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SshKey {
pub id: SshKeyId,
pub title: String,
pub key: String,
pub created_at: DateTime<Utc>,
pub can_push: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SshKeyWithUser {
pub id: SshKeyId,
pub title: String,
pub key: String,
pub created_at: DateTime<Utc>,
pub user: UserPublic,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoteType {
Commit,
Issue,
MergeRequest,
Snippet,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiscussionNoteType {
DiscussionNote,
DiffNote,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NoteableId {
Commit(ObjectId),
Issue(IssueId),
MergeRequest(MergeRequestId),
Snippet(SnippetId),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NoteableInternalId {
Issue(IssueInternalId),
MergeRequest(MergeRequestInternalId),
}
impl_id!(NoteId, "Type-safe note (comment) ID.");
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum NotePositionType {
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NotePosition {
pub base_sha: ObjectId,
pub start_sha: ObjectId,
pub head_sha: ObjectId,
pub position_type: NotePositionType,
pub old_path: String,
pub new_path: String,
pub old_line: Option<u64>,
pub new_line: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Note {
pub id: NoteId,
#[serde(rename = "type")]
pub note_type: Option<DiscussionNoteType>,
pub body: String,
pub attachment: Option<String>,
pub author: UserBasic,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub resolvable: bool,
pub resolved: Option<bool>,
pub resolved_by: Option<UserBasic>,
pub system: bool,
noteable_id: Value,
noteable_iid: Option<Value>,
pub noteable_type: NoteType,
pub position: Option<NotePosition>,
}
impl Note {
pub fn noteable_id(&self) -> Option<NoteableId> {
match self.noteable_type {
NoteType::Commit => {
self.noteable_id
.as_str()
.map(|id| NoteableId::Commit(ObjectId::new(id)))
},
NoteType::Issue => {
self.noteable_id
.as_u64()
.map(|id| NoteableId::Issue(IssueId::new(id)))
},
NoteType::MergeRequest => {
self.noteable_id
.as_u64()
.map(|id| NoteableId::MergeRequest(MergeRequestId::new(id)))
},
NoteType::Snippet => {
self.noteable_id
.as_u64()
.map(|id| NoteableId::Snippet(SnippetId::new(id)))
},
}
}
pub fn noteable_iid(&self) -> Option<NoteableInternalId> {
match self.noteable_type {
NoteType::Commit => None,
NoteType::Issue => {
self.noteable_iid
.as_ref()
.and_then(|value| value.as_u64())
.map(|id| NoteableInternalId::Issue(IssueInternalId::new(id)))
},
NoteType::MergeRequest => {
self.noteable_iid
.as_ref()
.and_then(|value| value.as_u64())
.map(|id| NoteableInternalId::MergeRequest(MergeRequestInternalId::new(id)))
},
NoteType::Snippet => None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Discussion {
pub id: ObjectId,
pub individual_note: bool,
pub notes: Vec<Note>,
}
impl_id!(AwardId, "Type-safe award ID.");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AwardableId {
Issue(IssueId),
MergeRequest(MergeRequestId),
Snippet(SnippetId),
Note(NoteId),
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum AwardableType {
Issue,
MergeRequest,
Snippet,
Note,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AwardEmoji {
pub id: AwardId,
pub name: String,
pub user: UserBasic,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
awardable_id: u64,
pub awardable_type: AwardableType,
}
impl AwardEmoji {
pub fn awardable_id(&self) -> AwardableId {
match self.awardable_type {
AwardableType::Issue => AwardableId::Issue(IssueId::new(self.awardable_id)),
AwardableType::MergeRequest => {
AwardableId::MergeRequest(MergeRequestId::new(self.awardable_id))
},
AwardableType::Snippet => AwardableId::Snippet(SnippetId::new(self.awardable_id)),
AwardableType::Note => AwardableId::Note(NoteId::new(self.awardable_id)),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineType {
#[serde(rename = "new")]
New,
#[serde(rename = "old")]
Old,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommitNote {
pub note: String,
pub path: Option<String>,
pub line: Option<u64>,
pub line_type: Option<LineType>,
pub author: UserBasic,
pub created_at: DateTime<Utc>,
}
impl_id!(CommitStatusId, "Type-safe commit status ID.");
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatusState {
#[serde(rename = "created")]
Created,
#[serde(rename = "pending")]
Pending,
#[serde(rename = "running")]
Running,
#[serde(rename = "success")]
Success,
#[serde(rename = "failed")]
Failed,
#[serde(rename = "canceled")]
Canceled,
#[serde(rename = "skipped")]
Skipped,
#[serde(rename = "manual")]
Manual,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommitStatus {
pub id: CommitStatusId,
pub sha: ObjectId,
#[serde(rename = "ref")]
pub ref_: Option<String>,
pub status: StatusState,
pub name: String,
pub target_url: Option<String>,
pub description: Option<String>,
pub created_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub finished_at: Option<DateTime<Utc>>,
pub allow_failure: bool,
pub coverage: Option<f64>,
pub author: UserBasic,
}
impl_id!(EnvironmentId, "Type-safe environment ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Environment {
pub id: EnvironmentId,
pub name: String,
pub slug: String,
pub external_url: Option<String>,
pub state: Option<String>,
pub last_deployment: Option<Deployment>,
}
impl_id!(DeploymentId, "Type-safe deployment ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Deployment {
pub id: DeploymentId,
pub iid: u64,
pub r#ref: String,
pub sha: String,
pub created_at: String,
pub status: Option<String>,
pub user: UserBasic,
pub deployable: Deployable,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Deployable {
pub commit: Commit,
pub status: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Commit {
pub id: Option<String>,
pub short_id: Option<String>,
pub created_at: Option<String>,
pub title: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventTargetType {
#[serde(rename = "commit")]
Commit,
#[serde(rename = "issue")]
Issue,
#[serde(rename = "merge_request")]
MergeRequest,
#[serde(rename = "snippet")]
Snippet,
#[serde(rename = "project_snippet")]
ProjectSnippet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventTargetId {
Commit(ObjectId),
Issue(IssueId),
MergeRequest(MergeRequestId),
Snippet(SnippetId),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Event {
pub title: Option<String>,
pub project_id: ProjectId,
pub action_name: String,
target_id: Value,
pub target_type: EventTargetType,
pub author_id: UserId,
pub data: Option<Value>,
pub target_title: String,
pub created_at: DateTime<Utc>,
pub note: Option<Note>,
pub author: Option<UserBasic>,
pub author_username: Option<String>,
}
impl Event {
pub fn target_id(&self) -> Option<EventTargetId> {
match self.target_type {
EventTargetType::Commit => {
self.target_id
.as_str()
.map(|id| EventTargetId::Commit(ObjectId(id.into())))
},
EventTargetType::Issue => {
self.target_id
.as_u64()
.map(|id| EventTargetId::Issue(IssueId(id)))
},
EventTargetType::MergeRequest => {
self.target_id
.as_u64()
.map(|id| EventTargetId::MergeRequest(MergeRequestId(id)))
},
EventTargetType::Snippet => {
self.target_id
.as_u64()
.map(|id| EventTargetId::Snippet(SnippetId(id)))
},
EventTargetType::ProjectSnippet => {
self.target_id
.as_u64()
.map(|id| EventTargetId::Snippet(SnippetId(id)))
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamespaceKind {
#[serde(rename = "user")]
User,
#[serde(rename = "group")]
Group,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamespaceId {
User(UserId),
Group(GroupId),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Namespace {
id: u64,
pub path: String,
pub name: String,
pub kind: NamespaceKind,
pub full_path: String,
pub members_count_with_descendants: Option<u64>,
pub avatar_url: Option<String>,
pub web_url: String,
}
impl Namespace {
pub fn id(&self) -> NamespaceId {
match self.kind {
NamespaceKind::User => NamespaceId::User(UserId(self.id)),
NamespaceKind::Group => NamespaceId::Group(GroupId(self.id)),
}
}
}
impl_id!(RunnerId, "Type-safe runner ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Runner {
pub id: RunnerId,
pub description: Option<String>,
pub active: bool,
pub is_shared: bool,
pub name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct JobArtifactFile {
pub filename: String,
pub size: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct JobArtifact {
pub file_type: String,
pub file_format: Option<String>,
pub filename: String,
pub size: u64,
}
impl_id!(JobId, "Type-safe job ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Job {
pub id: JobId,
pub status: StatusState,
pub stage: String,
pub name: String,
#[serde(rename = "ref")]
pub ref_: Option<String>,
pub tag: bool,
pub coverage: Option<f64>,
pub created_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub finished_at: Option<DateTime<Utc>>,
pub user: Option<User>,
pub artifacts_file: Option<JobArtifactFile>,
pub commit: RepoCommit,
pub runner: Option<Runner>,
pub pipeline: PipelineBasic,
pub allow_failure: bool,
pub duration: Option<f64>,
pub artifacts: Vec<JobArtifact>,
pub artifacts_expire_at: Option<DateTime<Utc>>,
pub web_url: String,
}
impl_id!(PipelineId, "Type-safe pipeline ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PipelineBasic {
pub id: PipelineId,
#[serde(rename = "ref")]
pub ref_: Option<String>,
pub sha: ObjectId,
pub status: StatusState,
pub created_at: Option<DateTime<Utc>>,
pub updated_at: Option<DateTime<Utc>>,
pub web_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Pipeline {
pub id: PipelineId,
pub sha: ObjectId,
#[serde(rename = "ref")]
pub ref_: Option<String>,
pub status: StatusState,
pub web_url: String,
pub before_sha: Option<ObjectId>,
pub tag: bool,
pub yaml_errors: Option<String>,
pub created_at: Option<DateTime<Utc>>,
pub updated_at: Option<DateTime<Utc>>,
pub started_at: Option<DateTime<Utc>>,
pub finished_at: Option<DateTime<Utc>>,
pub committed_at: Option<DateTime<Utc>>,
pub duration: Option<u64>,
pub coverage: Option<String>,
pub user: UserBasic,
pub detailed_status: Value,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PipelineVariableType {
#[serde(rename = "env_var")]
EnvVar,
#[serde(rename = "file")]
File,
}
impl Default for PipelineVariableType {
fn default() -> Self {
PipelineVariableType::EnvVar
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PipelineVariable {
pub key: String,
pub value: String,
#[serde(default)]
pub variable_type: PipelineVariableType,
}
impl_id!(LabelEventId, "Type-safe label event ID.");
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResourceLabelEvent {
pub id: LabelEventId,
pub user: UserBasic,
pub created_at: DateTime<Utc>,
resource_id: u64,
resource_type: String,
pub label: Option<EventLabel>,
pub action: String,
}
impl ResourceLabelEvent {
pub fn event_target(&self) -> Option<ResourceLabelEventTarget> {
match self.resource_type.as_ref() {
"MergeRequest" => {
Some(ResourceLabelEventTarget::MergeRequest(MergeRequestId::new(
self.resource_id,
)))
},
"Issue" => {
Some(ResourceLabelEventTarget::Issue(IssueId::new(
self.resource_id,
)))
},
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResourceLabelEventTarget {
Issue(IssueId),
MergeRequest(MergeRequestId),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EventLabel {
pub id: LabelId,
pub name: String,
pub color: LabelColor,
pub description: Option<String>,
}