coodev-runner 0.1.42

A simple runner for coodev
Documentation
use crate::{error, WorkflowAPIEvent, WorkflowEvent, WorkflowState, WorkflowTriggerEvents};

pub fn is_workflow_finished(state: &WorkflowState) -> bool {
  match state {
    WorkflowState::Succeeded => true,
    WorkflowState::Failed => true,
    WorkflowState::Cancelled => true,
    WorkflowState::Skipped => true,
    _ => false,
  }
}

pub fn get_workflow_event_payload(event: WorkflowEvent) -> crate::Result<WorkflowAPIEvent> {
  match event {
    WorkflowEvent::API(api_event) => Ok(api_event),
    WorkflowEvent::PullRequest(event) => {
      let api_event = WorkflowAPIEvent {
        repo_owner: event.repository.owner.login,
        repo_name: event.repository.name,
        ref_name: event.pull_request.base.ref_name,
        sha: event.pull_request.head.sha,
        pr_number: Some(event.pull_request.number),
      };

      Ok(api_event)
    }
    WorkflowEvent::Push(event) => {
      let api_event = WorkflowAPIEvent {
        repo_owner: event.repository.owner.login,
        repo_name: event.repository.name,
        ref_name: event.ref_name,
        sha: event.after,
        pr_number: None,
      };

      Ok(api_event)
    }
    #[allow(unreachable_patterns)]
    _ => Err(error::Error::unsupported_feature(
      "Currently only support API events",
    )),
  }
}

pub fn should_skip_workflow(
  on: &WorkflowTriggerEvents,
  event: &WorkflowEvent,
  changed_files: &Option<Vec<String>>,
) -> anyhow::Result<bool> {
  match event {
    WorkflowEvent::API(_) => Ok(false),
    WorkflowEvent::PullRequest(pull_request) => match &on.pull_request {
      Some(pull_request_on) => {
        if let Some(types) = &pull_request_on.types {
          if !types.contains(&pull_request.action) {
            return Ok(true);
          }
        }

        if let (Some(patterns), Some(changed_files)) = (&pull_request_on.paths, changed_files) {
          if !is_match_patterns(changed_files, patterns)? {
            return Ok(true);
          }
        }

        if let Some(branches) = &pull_request_on.branches {
          let ref_name = pull_request
            .pull_request
            .base
            .ref_name
            .replace("refs/heads/", "");
          if !is_match_patterns(&vec![ref_name], branches)? {
            return Ok(true);
          }
        }

        return Ok(false);
      }
      None => {
        return Ok(false);
      }
    },
    WorkflowEvent::Push(push_event) => match &on.push {
      Some(push_on) => {
        let is_tag = push_event.ref_name.starts_with("refs/tags/");

        if is_tag {
          if let Some(tags) = &push_on.tags {
            let tag_name = push_event.ref_name.replace("refs/tags/", "");
            if !is_match_patterns(&vec![tag_name], tags)? {
              return Ok(true);
            }
          }
        } else {
          if let Some(branches) = &push_on.branches {
            let ref_name = push_event.ref_name.replace("refs/heads/", "");
            if !is_match_patterns(&vec![ref_name], branches)? {
              return Ok(true);
            }
          }
        }

        if let (Some(patterns), Some(changed_files)) = (&push_on.paths, changed_files) {
          if !is_match_patterns(changed_files, patterns)? {
            return Ok(true);
          }
        }

        return Ok(false);
      }
      None => {
        return Ok(false);
      }
    },
    #[allow(unreachable_patterns)]
    _ => {
      todo!("Only API events are supported for now")
    }
  }
}

fn is_match_patterns(
  values: &Vec<String>,
  patterns: &Vec<String>,
) -> Result<bool, glob::PatternError> {
  for value in values {
    for pattern in patterns {
      if glob::Pattern::new(pattern)?.matches(value) {
        return Ok(true);
      }
    }
  }

  Ok(false)
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn is_match_workflow_paths() {
    let paths = vec![
      "src/main.rs".to_string(),
      "src/lib.rs".to_string(),
      "src/runner/main.rs".to_string(),
      "src/runner/lib.rs".to_string(),
    ];

    assert_eq!(
      is_match_patterns(&paths, &vec!["src/main.rs".to_string()]).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&paths, &vec!["src/runner/main.rs".to_string()]).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&paths, &vec!["src/runner/*.rs".to_string()]).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&paths, &vec!["src/runner/**/*.rs".to_string()]).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&paths, &vec!["src/runner/**/main.rs".to_string()]).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&paths, &vec!["src/runner/**/lib.rs".to_string()]).unwrap(),
      true
    );

    // Negative tests
    assert_eq!(
      is_match_patterns(&paths, &vec!["scripts/**/lib.rs".to_string()]).unwrap(),
      false
    );

    assert_eq!(
      is_match_patterns(&paths, &vec!["src/runner/**/lib.js".to_string()]).unwrap(),
      false
    );
  }

  #[test]
  fn is_match_branches() {
    let branches = vec!["master".to_string(), "develop".to_string()];

    assert_eq!(
      is_match_patterns(&vec!["master".to_string()], &branches).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&vec!["develop".to_string()], &branches).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&vec!["feature/branch".to_string()], &branches).unwrap(),
      false
    );
  }

  #[test]
  fn is_match_features() {
    let features = vec!["feature/*".to_string()];

    assert_eq!(
      is_match_patterns(&vec!["feature/branch".to_string()], &features).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&vec!["feature/branch/branch".to_string()], &features).unwrap(),
      true
    );

    assert_eq!(
      is_match_patterns(&vec!["feature".to_string()], &features).unwrap(),
      false
    );

    assert_eq!(
      is_match_patterns(&vec!["feature-branch".to_string()], &features).unwrap(),
      false
    );
  }
}