pub trait GitlabEventExt { fn project(&self) -> &Project; fn r#ref(&self) -> &str; fn user(&self) -> &str; fn url(&self) -> String; fn title(&self) -> String; } #[derive(Debug, Deserialize)] pub struct User { name: String, } #[derive(Debug, Deserialize)] pub struct Project { pub name: String, pub description: String, pub web_url: String, pub avatar_url: Option, pub namespace: String, pub path_with_namespace: String, pub default_branch: String, } #[derive(Debug, Deserialize)] pub struct Repository { pub name: String, pub url: String, pub description: String, } #[derive(Debug, Deserialize)] pub struct Commit { pub id: String, pub title: String, pub url: String, } #[derive(PartialEq, Debug, Deserialize)] pub enum MergeRequestAction { #[serde(rename = "open")] Opened, #[serde(rename = "close")] Closed, #[serde(rename = "reopen")] Reopened, #[serde(rename = "update")] Updated, #[serde(rename = "approved")] Approved, #[serde(rename = "unapproved")] Unapproved, #[serde(rename = "merge")] Merged, #[serde(other)] Other, } impl MergeRequestAction { pub fn as_str(&self) -> &str { match self { MergeRequestAction::Opened => "opened", MergeRequestAction::Closed => "closed", MergeRequestAction::Reopened => "reopened", MergeRequestAction::Updated => "updated", MergeRequestAction::Approved => "approved", MergeRequestAction::Unapproved => "unapproved", MergeRequestAction::Merged => "merged", MergeRequestAction::Other => "other", } } } #[derive(Debug, Deserialize)] pub struct MergeRequestObjectAttributes { pub target_branch: String, pub source_branch: String, pub title: String, pub url: String, pub source: Project, pub target: Project, pub action: MergeRequestAction, } #[derive(PartialEq, Debug, Deserialize)] pub enum PipelineStatus { #[serde(rename = "failed")] Failed, #[serde(other)] Other, } impl PipelineStatus { pub fn as_str(&self) -> &str { match self { PipelineStatus::Failed => "failed", PipelineStatus::Other => "other", } } } #[derive(Debug, Deserialize)] pub struct PipelineObjectAttributes { pub name: Option, pub r#ref: String, pub status: PipelineStatus, pub url: String, } #[derive(Debug, Deserialize)] pub struct PipelineMergeRequest { pub title: String, } #[derive(Debug, Deserialize)] #[serde(tag = "object_kind")] pub enum GitlabEvent { #[serde(rename = "push")] Push { event_name: String, before: String, after: String, r#ref: String, ref_protected: bool, checkout_sha: String, user_id: u64, user_name: String, user_username: String, user_email: String, user_avatar: Option, project_id: u64, project: Project, repository: Repository, commits: Vec, total_commits_count: u64, }, #[serde(rename = "tag_push")] TagPush { event_name: String, before: String, after: String, r#ref: String, ref_protected: bool, checkout_sha: String, user_id: u64, user_name: String, user_avatar: Option, project_id: u64, project: Project, repository: Repository, commits: Vec, total_commits_count: u64, }, #[serde(rename = "merge_request")] MergeRequest { user: User, project: Project, repository: Repository, object_attributes: MergeRequestObjectAttributes, }, #[serde(rename = "pipeline")] Pipeline { object_attributes: PipelineObjectAttributes, merge_request: PipelineMergeRequest, user: User, project: Project, }, } impl GitlabEventExt for GitlabEvent { fn project(&self) -> &Project { match self { GitlabEvent::Push { project, .. } => &project, GitlabEvent::TagPush { project, .. } => &project, GitlabEvent::MergeRequest { project, .. } => &project, GitlabEvent::Pipeline { project, .. } => &project, } } fn r#ref(&self) -> &str { match self { GitlabEvent::Push { r#ref, .. } => &r#ref, GitlabEvent::TagPush { r#ref, .. } => &r#ref, GitlabEvent::MergeRequest { object_attributes, .. } => &object_attributes.target_branch, GitlabEvent::Pipeline { object_attributes, .. } => &object_attributes.r#ref, } } fn user(&self) -> &str { match self { GitlabEvent::Push { user_name, .. } => &user_name, GitlabEvent::TagPush { user_name, .. } => &user_name, GitlabEvent::MergeRequest { user, .. } => &user.name, GitlabEvent::Pipeline { user, .. } => &user.name, } } fn url(&self) -> String { let url = match self { GitlabEvent::Push { after, project, .. } => format!("{}/-/commits/{}", project.web_url, after), GitlabEvent::TagPush { r#ref, checkout_sha, project, .. } => { let refname = r#ref.split('/').into_iter().last().unwrap_or(checkout_sha); format!("{}/-/tags/{}", project.web_url, refname) } GitlabEvent::MergeRequest { object_attributes, .. } => object_attributes.url.clone(), GitlabEvent::Pipeline { object_attributes, .. } => object_attributes.url.clone(), }; url.replace("http://", "https://").to_string() } fn title(&self) -> String { fn find_commit<'a>(commits: &'a Vec, sha: &str) -> Option<&'a Commit> { commits.iter().find(|commit| commit.id == sha) } match self { GitlabEvent::Push { after, commits, .. } => find_commit(commits, &after) .map(|commit| commit.title.clone()) .unwrap_or_else(|| "New commit(s) pushed".to_string()), GitlabEvent::TagPush { checkout_sha, commits, .. } => find_commit(commits, &checkout_sha) .map(|commit| commit.title.clone()) .unwrap_or_else(|| "New tag pushed".to_string()), GitlabEvent::MergeRequest { object_attributes, .. } => { format!("MR {}: {}", object_attributes.action.as_str(), object_attributes.title) } GitlabEvent::Pipeline { object_attributes, merge_request, .. } => { let title = object_attributes.name.as_ref().unwrap_or(&merge_request.title); format!("Pipeline {}: {}", object_attributes.status.as_str(), title) } } } } #[cfg(test)] mod test { use super::*; use std::{fs::File, io::BufReader}; fn load_test_data(name: &str) -> anyhow::Result { let f = File::open(&format!("{}/test-data/{}.json", env!("CARGO_MANIFEST_DIR"), name))?; let r = BufReader::new(f); let event: GitlabEvent = serde_json::from_reader(r)?; Ok(event) } #[test] pub fn parse_push_event() -> anyhow::Result<()> { let event = load_test_data("push-event")?; match event { GitlabEvent::Push { event_name, before, after, r#ref, checkout_sha, user_username, project, repository, total_commits_count, .. } => { assert_eq!(event_name, "push"); assert_eq!(before, "95790bf891e76fee5e1747ab589903a6a1f80f22"); assert_eq!(after, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7"); assert_eq!(r#ref, "refs/heads/master"); assert_eq!(checkout_sha, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7"); assert_eq!(user_username, "jsmith"); assert_eq!(project.name, "Diaspora"); assert_eq!(project.namespace, "Mike"); assert_eq!(repository.name, "Diaspora"); assert_eq!(repository.url, "git@example.com:mike/diaspora.git"); assert_eq!(total_commits_count, 4); } _ => panic!("not a push event"), }; Ok(()) } #[test] pub fn parse_tag_push_event() -> anyhow::Result<()> { let event = load_test_data("tag-push-event")?; match event { GitlabEvent::TagPush { event_name, before, after, r#ref, checkout_sha, user_name, project, repository, total_commits_count, .. } => { assert_eq!(event_name, "tag_push"); assert_eq!(before, "0000000000000000000000000000000000000000"); assert_eq!(after, "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7"); assert_eq!(r#ref, "refs/tags/v1.0.0"); assert_eq!(checkout_sha, "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7"); assert_eq!(user_name, "John Smith"); assert_eq!(project.name, "Example"); assert_eq!(project.namespace, "Jsmith"); assert_eq!(repository.name, "Example"); assert_eq!(total_commits_count, 0); } _ => panic!("not a tag push event"), }; Ok(()) } #[test] pub fn parse_merge_request_event() -> anyhow::Result<()> { let event = load_test_data("merge-request-event")?; match event { GitlabEvent::MergeRequest { user, object_attributes, .. } => { assert_eq!(user.name, "Administrator"); assert_eq!(object_attributes.action, MergeRequestAction::Opened); assert_eq!(object_attributes.title, "MS-Viewport"); } _ => panic!("not a merge request event"), }; Ok(()) } #[test] pub fn parse_pipeline_event() -> anyhow::Result<()> { let event = load_test_data("pipeline-event")?; match event { GitlabEvent::Pipeline { object_attributes, merge_request, user, .. } => { assert_eq!(object_attributes.name, Some("Pipeline for branch: master".to_string())); assert_eq!(object_attributes.r#ref, "master"); assert_eq!(object_attributes.status, PipelineStatus::Failed); assert_eq!(merge_request.title, "Test"); assert_eq!(user.name, "Administrator"); } _ => panic!("not a pipeline event"), }; Ok(()) } }