use crates::chrono::{DateTime, NaiveDate, TimeZone, Utc};
use crates::serde::de::{Error, Unexpected};
use crates::serde::{Deserialize, Deserializer, Serialize, Serializer};
use crates::serde_json::{self, Value};
use types::{
IssueId, IssueInternalId, IssueState, JobId, MergeRequestId, MergeRequestInternalId,
MergeRequestState, MergeStatus, MilestoneId, NoteId, NoteType, NoteableId, ObjectId, ProjectId,
SnippetId, UserId,
};
#[derive(Debug, Clone, Copy)]
pub struct HookDate(DateTime<Utc>);
impl Serialize for HookDate {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for HookDate {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let val = String::deserialize(deserializer)?;
Utc.datetime_from_str(&val, "%Y-%m-%d %H:%M:%S UTC")
.or_else(|_| DateTime::parse_from_rfc3339(&val).map(|dt| dt.with_timezone(&Utc)))
.or_else(|_| {
DateTime::parse_from_str(&val, "%Y-%m-%d %H:%M:%S %z")
.map(|dt| dt.with_timezone(&Utc))
})
.map_err(|err| {
D::Error::invalid_value(
Unexpected::Other("hook date"),
&format!("Unsupported format: {} {:?}", val, err).as_str(),
)
})
.map(HookDate)
}
}
impl AsRef<DateTime<Utc>> for HookDate {
fn as_ref(&self) -> &DateTime<Utc> {
&self.0
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProjectHookAttrs {
pub name: String,
pub description: Option<String>,
pub web_url: String,
pub avatar_url: Option<String>,
pub git_ssh_url: String,
pub git_http_url: String,
pub namespace: String,
pub visibility_level: u64,
pub path_with_namespace: String,
pub default_branch: Option<String>,
homepage: String,
http_url: String,
ssh_url: String,
url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProjectWikiHookAttrs {
pub web_url: String,
pub git_ssh_url: String,
pub git_http_url: String,
pub path_with_namespace: String,
pub default_branch: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserHookAttrs {
pub name: String,
pub username: String,
pub avatar_url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HookCommitIdentity {
pub name: String,
pub email: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommitHookAttrs {
pub id: ObjectId,
pub message: String,
pub timestamp: DateTime<Utc>,
pub url: String,
pub author: HookCommitIdentity,
pub added: Option<Vec<String>>,
pub modified: Option<Vec<String>>,
pub removed: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PushHook {
pub object_kind: String,
event_name: String,
pub before: ObjectId,
pub after: ObjectId,
#[serde(rename = "ref")]
pub ref_: String,
pub checkout_sha: Option<ObjectId>,
pub message: Option<String>,
pub user_id: UserId,
pub user_name: String,
pub user_username: String,
pub user_email: String,
pub user_avatar: Option<String>,
pub project_id: ProjectId,
pub project: ProjectHookAttrs,
pub commits: Vec<CommitHookAttrs>, pub total_commits_count: u64,
repository: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueAction {
Update,
Open,
Close,
Reopen,
}
enum_serialize!(IssueAction -> "issue action",
Update => "update",
Open => "open",
Close => "close",
Reopen => "reopen",
);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IssueHookAttrs {
pub id: IssueId,
pub title: String,
pub assignee_id: Option<UserId>,
pub author_id: UserId,
pub project_id: ProjectId,
pub created_at: HookDate,
pub updated_at: HookDate,
pub deleted_at: Option<HookDate>,
pub closed_at: Option<HookDate>,
pub due_date: Option<NaiveDate>,
pub updated_by_id: Option<UserId>,
pub moved_to_id: Option<Value>, pub branch_name: Option<String>,
pub description: Option<String>,
pub milestone_id: Option<MilestoneId>,
pub state: IssueState,
pub iid: IssueInternalId,
pub confidential: bool,
pub time_estimate: u64,
pub total_time_spent: u64,
pub human_time_estimate: Option<String>,
pub human_total_time_spent: Option<String>,
pub url: Option<String>,
pub action: Option<IssueAction>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IssueHook {
pub object_kind: String,
pub user: UserHookAttrs,
pub project: ProjectHookAttrs,
pub object_attributes: IssueHookAttrs,
pub assignee: Option<UserHookAttrs>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeRequestAction {
Update,
Open,
Close,
Reopen,
Merge,
}
enum_serialize!(MergeRequestAction -> "merge request action",
Update => "update",
Open => "open",
Close => "close",
Reopen => "reopen",
Merge => "merge",
);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MergeRequestParams {
force_remove_source_branch: Option<Value>, }
impl MergeRequestParams {
pub fn force_remove_source_branch(&self) -> bool {
self.force_remove_source_branch
.as_ref()
.map_or(false, |val| {
if let Some(as_str) = val.as_str() {
as_str == "1"
} else if let Some(as_bool) = val.as_bool() {
as_bool
} else {
error!(
target: "gitlab",
"unknown value for force_remove_source_branch: {}",
val,
);
false
}
})
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MergeRequestHookAttrs {
pub source: Option<ProjectHookAttrs>,
pub target: ProjectHookAttrs,
pub last_commit: Option<CommitHookAttrs>,
pub work_in_progress: bool,
pub in_progress_merge_commit_sha: Option<ObjectId>,
pub id: MergeRequestId,
pub target_branch: String,
pub target_project_id: ProjectId,
pub source_branch: String,
pub source_project_id: Option<ProjectId>,
pub author_id: UserId,
pub assignee_id: Option<UserId>,
pub title: String,
pub created_at: HookDate,
pub updated_at: HookDate,
pub deleted_at: Option<HookDate>,
pub locked_at: Option<HookDate>,
pub updated_by_id: Option<UserId>,
pub merge_commit_sha: Option<ObjectId>,
pub merge_error: Option<Value>, pub merge_params: MergeRequestParams,
pub merge_user_id: Option<UserId>,
pub merge_when_pipeline_succeeds: bool,
pub milestone_id: Option<MilestoneId>,
pub oldrev: Option<ObjectId>,
pub state: MergeRequestState,
pub merge_status: MergeStatus,
pub iid: MergeRequestInternalId,
pub description: Option<String>,
pub url: Option<String>,
pub action: Option<MergeRequestAction>,
pub time_estimate: u64,
lock_version: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MergeRequestHook {
pub object_kind: String,
pub user: UserHookAttrs,
pub project: ProjectHookAttrs,
pub object_attributes: MergeRequestHookAttrs,
pub assignee: Option<UserHookAttrs>,
repository: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SnippetType {
Project,
Personal,
}
enum_serialize!(SnippetType -> "snippet type",
Project => "ProjectSnippet",
Personal => "PersonalSnippet",
);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SnippetHookAttrs {
pub title: String,
pub content: String,
pub author_id: UserId,
pub project_id: Option<ProjectId>,
pub created_at: HookDate,
pub updated_at: HookDate,
pub file_name: String,
#[serde(rename = "type")]
pub type_: SnippetType,
pub visibility_level: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WikiPageAction {
Create,
Update,
}
enum_serialize!(WikiPageAction -> "wiki page action",
Create => "create",
Update => "update",
);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WikiPageHookAttrs {
pub title: String,
pub content: String,
pub format: String,
pub message: String,
pub slug: String,
pub url: String,
pub action: WikiPageAction,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DiffHookAttrs {
pub diff: String,
pub new_path: String,
pub old_path: String,
pub a_mode: String,
pub b_mode: String,
pub new_file: bool,
pub renamed_file: bool,
pub deleted_file: bool,
pub too_large: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PositionHookAttrs {
pub base_sha: ObjectId,
pub head_sha: ObjectId,
pub start_sha: ObjectId,
pub old_line: Option<u64>,
pub old_path: String,
pub new_line: Option<u64>,
pub new_path: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NoteHookAttrs {
pub id: NoteId,
pub note: String,
pub noteable_type: NoteType,
original_position: Value,
position: Value,
pub author_id: UserId,
pub created_at: HookDate,
pub updated_at: HookDate,
pub updated_by_id: Option<UserId>,
pub resolved_at: Option<HookDate>,
pub resolved_by_id: Option<UserId>,
pub project_id: ProjectId,
pub attachment: Option<String>,
pub line_code: Option<String>, pub commit_id: Option<ObjectId>, pub discussion_id: ObjectId,
pub original_discussion_id: Option<ObjectId>,
noteable_id: Value, pub system: bool,
pub st_diff: Option<DiffHookAttrs>,
pub url: String,
#[serde(rename = "type")]
pub type_: Option<String>,
}
impl NoteHookAttrs {
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)))
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NoteHook {
pub object_kind: String,
pub user: UserHookAttrs,
pub project_id: ProjectId,
pub project: ProjectHookAttrs,
pub object_attributes: NoteHookAttrs,
pub commit: Option<CommitHookAttrs>,
pub issue: Option<IssueHookAttrs>,
pub merge_request: Option<MergeRequestHookAttrs>,
pub snippet: Option<SnippetHookAttrs>,
repository: Value,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BuildUserHookAttrs {
pub id: Option<UserId>,
pub name: Option<String>,
pub email: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BuildCommitHookAttrs {
pub id: String,
pub sha: ObjectId,
pub message: String,
pub author_name: String,
pub author_email: String,
pub status: String,
pub duration: u64,
pub started_at: Option<HookDate>,
pub finished_at: Option<HookDate>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BuildProjectHookAttrs {
pub name: String,
pub description: Option<String>,
pub homepage: String,
pub git_http_url: String,
pub git_ssh_url: String,
pub visibility_level: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BuildHook {
pub object_kind: String,
#[serde(rename = "ref")]
pub ref_: String,
pub tag: String,
pub before_sha: String,
pub sha: String,
pub build_id: JobId,
pub build_name: String,
pub build_stage: String,
pub build_started_at: Option<HookDate>,
pub build_finished_at: Option<HookDate>,
pub build_duration: Option<u64>,
pub build_allow_failure: bool,
pub project_id: ProjectId,
pub user: BuildUserHookAttrs,
pub commit: BuildCommitHookAttrs,
pub repository: BuildProjectHookAttrs,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WikiPageHook {
pub object_kind: String,
pub user: UserHookAttrs,
pub project: ProjectHookAttrs,
pub wiki: ProjectWikiHookAttrs,
pub object_attributes: WikiPageHookAttrs,
}
#[derive(Debug, Clone)]
pub enum WebHook {
Push(Box<PushHook>),
Issue(Box<IssueHook>),
MergeRequest(Box<MergeRequestHook>),
Note(Box<NoteHook>),
Build(Box<BuildHook>),
WikiPage(Box<WikiPageHook>),
}
impl<'de> Deserialize<'de> for WebHook {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let val = <Value as Deserialize>::deserialize(deserializer)?;
let object_kind = match val.pointer("/object_kind") {
Some(&Value::String(ref kind)) => kind.clone(),
Some(_) => {
return Err(D::Error::invalid_type(
Unexpected::Other("JSON value"),
&"a string",
));
},
None => {
return Err(D::Error::missing_field("object_kind"));
},
};
let hook_res = match object_kind.as_ref() {
"push" | "tag_push" => {
serde_json::from_value(val).map(|hook| WebHook::Push(Box::new(hook)))
},
"issue" => serde_json::from_value(val).map(|hook| WebHook::Issue(Box::new(hook))),
"merge_request" => {
serde_json::from_value(val).map(|hook| WebHook::MergeRequest(Box::new(hook)))
},
"note" => serde_json::from_value(val).map(|hook| WebHook::Note(Box::new(hook))),
"build" => serde_json::from_value(val).map(|hook| WebHook::Build(Box::new(hook))),
_ => {
return Err(D::Error::invalid_value(
Unexpected::Other("object kind"),
&format!("unrecognized webhook object kind: {}", object_kind).as_str(),
));
},
};
hook_res.map_err(|err| {
D::Error::invalid_value(
Unexpected::Other("web hook"),
&format!("{:?}", err).as_str(),
)
})
}
}