rem-bash 0.2.2

Remote bash script execution and library import
use anyhow::{bail, Result};
use async_trait::async_trait;
use serde::Deserialize;
use std::{
    convert::{From, TryFrom},
    env,
};
use url::Url;

use crate::{
    repo::{GenericRepo, Repo},
    Password,
};

pub const PROVIDER: &'static str = "gitlab";

#[derive(Debug, Deserialize)]
struct GitlabFileResponse {
    content: String,
}

#[derive(Debug, Deserialize)]
struct GitlabRepoResponse {
    id: u32,
}

pub struct GitlabRepo {
    project_id: String,
    token: Option<GitlabToken>,
}

enum GitlabToken {
    Saved(String),
    FromEnv(String),
}

#[async_trait]
impl Repo for GitlabRepo {
    fn id() -> &'static str {
        PROVIDER
    }

    async fn get(&self, path: &str, repo_ref: &str) -> Result<String> {
        let script_url = format!(
            "https://gitlab.com/api/v4/projects/{}/repository/files/{}?ref={}",
            self.project_id, path, repo_ref,
        );

        let req = reqwest::Client::new().get(script_url);
        let token = match &self.token {
            Some(GitlabToken::Saved(saved)) => Some(saved.clone()),
            Some(GitlabToken::FromEnv(var)) => Some(env::var(var)?),
            None => None,
        };

        let req = match token {
            Some(token) => req.header("PRIVATE-TOKEN", token),
            _ => req,
        };

        let resp = req.send().await?;
        if !resp.status().is_success() {
            bail!(
                "Got error response from gitlab: {}",
                resp.json::<serde_json::Value>().await?
            );
        }

        let resp = resp.json::<GitlabFileResponse>().await?;
        let decoded_content = base64::decode(resp.content)?;
        Ok(String::from_utf8(decoded_content)?)
    }
}

impl From<GitlabRepo> for GenericRepo {
    fn from(gitlab_repo: GitlabRepo) -> Self {
        let (password, password_env) = match gitlab_repo.token {
            Some(GitlabToken::Saved(saved)) => (Some(saved), None),
            Some(GitlabToken::FromEnv(var)) => (None, Some(var)),
            _ => (None, None),
        };

        GenericRepo {
            provider: GitlabRepo::id().to_string(),
            uri: gitlab_repo.project_id,
            username: None,
            password,
            password_env,
        }
    }
}

impl TryFrom<GenericRepo> for GitlabRepo {
    type Error = anyhow::Error;
    fn try_from(repo: GenericRepo) -> Result<Self> {
        let token = match (repo.password, repo.password_env) {
            (Some(_), Some(_)) => bail!("Gitlab repo cannot have both passsword and password_env"),
            (Some(saved), None) => Some(GitlabToken::Saved(saved)),
            (None, Some(var)) => Some(GitlabToken::FromEnv(var)),
            _ => None,
        };

        Ok(Self {
            project_id: repo.uri,
            token,
        })
    }
}

pub async fn fetch_project(uri: &Url, token: Password) -> Result<GenericRepo> {
    let without_leading_slash = uri.path().trim_start_matches('/');
    let encoded_uri = urlencoding::encode(without_leading_slash);
    let repo_url = format!("https://gitlab.com/api/v4/projects/{}", encoded_uri);
    let req = reqwest::Client::new().get(repo_url);

    let (req, token_to_save) = match token {
        Password::Saved(token) => (
            req.header("PRIVATE-TOKEN", token.clone()),
            Some(GitlabToken::Saved(token)),
        ),
        Password::FromEnv(var, token) => (
            req.header("PRIVATE-TOKEN", token),
            Some(GitlabToken::FromEnv(var)),
        ),
        _ => (req, None),
    };

    let resp = req.send().await?;
    if !resp.status().is_success() {
        bail!(
            "Got error response from gitlab: {}",
            resp.json::<serde_json::Value>().await?
        );
    }

    let resp = resp.json::<GitlabRepoResponse>().await?;
    let result = GitlabRepo {
        project_id: format!("{}", resp.id),
        token: token_to_save,
    };

    Ok(result.into())
}