add gitea crate

This commit is contained in:
Cadey Ratio 2020-07-08 17:35:08 -04:00
parent 9967e8f62f
commit 2eec34f267
8 changed files with 254 additions and 473 deletions

13
gitea/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "gitea"
version = "0.1.0"
authors = ["Christine Dodrill <me@christine.website>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1"
reqwest = { version = "0.10", features = ["json"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }

241
gitea/src/lib.rs Normal file
View File

@ -0,0 +1,241 @@
use reqwest::header;
use serde::{Deserialize, Serialize};
use std::result::Result as StdResult;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("error from reqwest: {0:#?}")]
Reqwest(#[from] reqwest::Error),
#[error("bad API token: {0:#?}")]
BadAPIToken(#[from] reqwest::header::InvalidHeaderValue),
#[error("error parsing/serializing json: {0:#?}")]
Json(#[from] serde_json::Error),
#[error("tag not found: {0}")]
TagNotFound(String),
}
pub type Result<T> = StdResult<T, Error>;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Author {
pub id: i64,
pub login: String,
pub full_name: String,
pub email: String,
pub avatar_url: String,
pub language: String,
pub is_admin: bool,
pub last_login: String,
pub created: String,
pub username: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Release {
pub id: i64,
pub tag_name: String,
pub target_commitish: String,
pub name: String,
pub body: String,
pub url: String,
pub tarball_url: String,
pub zipball_url: String,
pub draft: bool,
pub prerelease: bool,
pub created_at: String,
pub published_at: String,
pub author: Author,
pub assets: Vec<Attachment>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateRelease {
pub body: String,
pub draft: bool,
pub name: String,
pub prerelease: bool,
pub tag_name: String,
pub target_commitish: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attachment {
pub id: i64,
pub name: String,
pub size: i64,
pub download_count: i64,
pub created_at: String,
pub uuid: String,
pub browser_download_url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Repo {
pub allow_merge_commits: bool,
pub allow_rebase: bool,
pub allow_rebase_explicit: bool,
pub allow_squash_merge: bool,
pub archived: bool,
pub avatar_url: String,
pub clone_url: String,
pub created_at: String,
pub default_branch: String,
pub description: String,
pub empty: bool,
pub fork: bool,
pub forks_count: i64,
pub full_name: String,
pub has_issues: bool,
pub has_pull_requests: bool,
pub has_wiki: bool,
pub html_url: String,
pub id: i64,
pub ignore_whitespace_conflicts: bool,
pub mirror: bool,
pub name: String,
pub open_issues_count: i64,
pub open_pr_counter: i64,
pub original_url: String,
pub owner: User,
pub permissions: Permissions,
pub private: bool,
pub release_counter: i64,
pub size: i64,
pub ssh_url: String,
pub stars_count: i64,
pub template: bool,
pub updated_at: String,
pub watchers_count: i64,
pub website: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct User {
pub avatar_url: String,
pub created: String,
pub email: String,
pub full_name: String,
pub id: i64,
pub is_admin: bool,
pub language: String,
pub last_login: String,
pub login: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Permissions {
pub admin: bool,
pub pull: bool,
pub push: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Version {
pub version: String,
}
pub struct Client {
cli: reqwest::Client,
base_url: String,
}
impl Client {
pub fn new(base_url: String, token: String, user_agent: String) -> Result<Self> {
let mut headers = header::HeaderMap::new();
let auth = format!("token {}", token);
let auth = auth.as_str();
headers.insert(header::AUTHORIZATION, header::HeaderValue::from_str(auth)?);
let cli = reqwest::Client::builder()
.user_agent(user_agent)
.default_headers(headers)
.build()?;
Ok(Self {
cli: cli,
base_url: base_url,
})
}
pub async fn version(&self) -> Result<Version> {
Ok(self
.cli
.get(&format!("{}/api/v1/version", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn get_release_by_tag(
&self,
owner: String,
repo: String,
tag: String,
) -> Result<Release> {
let releases: Vec<Release> = self.get_releases(owner, repo).await?;
let mut release: Option<Release> = None;
for rls in releases {
if *tag == rls.tag_name {
release = Some(rls);
}
}
match release {
None => Err(Error::TagNotFound(tag)),
Some(release) => Ok(release),
}
}
pub async fn get_repo(&self, owner: String, repo: String) -> Result<Repo> {
Ok(self
.cli
.get(&format!(
"{}/api/v1/repos/{}/{}",
self.base_url, owner, repo
))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn get_releases(&self, owner: String, repo: String) -> Result<Vec<Release>> {
Ok(self
.cli
.get(&format!(
"{}/api/v1/repos/{}/{}/releases",
self.base_url, owner, repo
))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn create_release(
&self,
owner: String,
repo: String,
cr: CreateRelease,
) -> Result<Release> {
Ok(self
.cli
.post(&format!(
"{}/api/v1/repos/{}/{}/releases",
self.base_url, owner, repo
))
.json(&cr)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
}

View File

@ -1,25 +0,0 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
pub(crate) async fn run(common: Common, tag: String) -> Result<()> {
let cli = client(&common)?;
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
let resp = cli
.delete(
format!(
"{}/api/v1/repos/{}/{}/releases/{}",
&common.server, &common.owner, &common.repo, release.id
)
.as_str(),
)
.send()
.await?;
if resp.status() != http::StatusCode::from_u16(204)? {
Err(anyhow!("wanted 204, got {}", resp.status()))
} else {
Ok(())
}
}

View File

@ -1,59 +0,0 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
use cli_table::{Cell, Row, Table};
use std::fs::File;
use std::io::Write;
pub(crate) async fn run(common: Common, fname: Option<PathBuf>, tag: String) -> Result<()> {
let cli = client(&common)?;
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
let attachments = get_attachments_for_release(
&cli,
&common.server,
&common.owner,
&common.repo,
&release.id,
)
.await?;
match fname {
None => {
let mut rows: Vec<Row> = vec![Row::new(vec![
Cell::new(&"name", Default::default()),
Cell::new(&"size", Default::default()),
Cell::new(&"url", Default::default()),
])];
for attachment in attachments {
rows.push(attachment.row())
}
let table = Table::new(rows, Default::default())?;
table.print_stdout()?;
Ok(())
}
Some(fname) => {
let mut url: Option<String> = None;
let fname = fname.into_os_string().into_string().unwrap();
for attachment in attachments {
if &fname == &attachment.name {
url = Some(attachment.browser_download_url);
}
}
if url.is_none() {
return Err(anyhow!("no attachment named {}", fname));
}
let data = &cli.get(url.unwrap().as_str()).send().await?.bytes().await?;
let mut fout = File::create(&fname)?;
fout.write(data)?;
Ok(())
}
}
}

View File

@ -1,48 +0,0 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
pub(crate) async fn run(
common: Common,
description: Option<String>,
rm: ReleaseMeta,
tag: String,
) -> Result<()> {
let cli = client(&common)?;
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
let mut cr = CreateRelease {
body: release.body,
draft: release.draft,
name: release.name,
prerelease: release.prerelease,
tag_name: release.tag_name,
target_commitish: release.target_commitish,
};
if let Some(description) = description {
cr.body = description;
}
if let Some(name) = rm.name {
cr.name = name;
}
cr.draft = rm.draft;
cr.prerelease = rm.pre_release;
let resp = cli
.post(&format!(
"{}/api/v1/repos/{}/{}/releases/{}",
common.server, common.owner, common.repo, release.id
))
.json(&cr)
.send()
.await?;
if !resp.status().is_success() {
return Err(anyhow!("{:?}", resp.status()));
}
Ok(())
}

View File

@ -1,97 +0,0 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
use cli_table::{Cell, Row, Table};
pub(crate) async fn run(common: Common, json: bool, tag: Option<String>) -> Result<()> {
let cli = client(&common)?;
let releases: Vec<Release> = cli
.get(
format!(
"{}/api/v1/repos/{}/{}/releases",
&common.server, &common.owner, &common.repo
)
.as_str(),
)
.send()
.await?
.json()
.await?;
match tag {
Some(tag) => {
let mut release: Option<Release> = None;
for rls in releases {
if tag == rls.tag_name {
release = Some(rls);
}
}
if release.is_none() {
return Err(anyhow!("tag {} not found", tag));
}
if json {
println!("{}", serde_json::to_string_pretty(&release)?);
} else {
let rls = release.unwrap();
let table = Table::new(
vec![
Row::new(vec![
Cell::new(&"id", Default::default()),
Cell::new(&rls.id, Default::default()),
]),
Row::new(vec![
Cell::new(&"author", Default::default()),
Cell::new(
&format!("{} - {}", rls.author.full_name, rls.author.username),
Default::default(),
),
]),
Row::new(vec![
Cell::new(&"tag", Default::default()),
Cell::new(&rls.tag_name, Default::default()),
]),
Row::new(vec![
Cell::new(&"created at", Default::default()),
Cell::new(&rls.created_at, Default::default()),
]),
Row::new(vec![
Cell::new(&"name", Default::default()),
Cell::new(&rls.name, Default::default()),
]),
Row::new(vec![
Cell::new(&"body", Default::default()),
Cell::new(&rls.body, Default::default()),
]),
],
Default::default(),
)?;
table.print_stdout()?;
}
}
None => {
if json {
println!("{}", serde_json::to_string_pretty(&releases)?);
} else {
let mut rows: Vec<Row> = vec![Row::new(vec![
Cell::new(&"id", Default::default()),
Cell::new(&"tag", Default::default()),
Cell::new(&"created at", Default::default()),
Cell::new(&"commit", Default::default()),
Cell::new(&"author", Default::default()),
Cell::new(&"name", Default::default()),
])];
for release in releases {
rows.push(release.row())
}
let table = Table::new(rows, Default::default())?;
table.print_stdout()?;
}
}
}
Ok(())
}

View File

@ -1,32 +0,0 @@
use crate::{gitea::*, *};
use anyhow::Result;
use reqwest::multipart;
use std::fs::File;
use std::io::Read;
pub(crate) async fn run(common: Common, fname: PathBuf, tag: String) -> Result<()> {
let cli = client(&common)?;
let bytes = {
let mut fin = File::open(&fname)?;
let mut buffer = Vec::new();
fin.read_to_end(&mut buffer)?;
buffer
};
let form = multipart::Form::new().part("attachment", multipart::Part::bytes(bytes));
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
cli.post(
format!(
"{}/api/v1/repos/{}/{}/releases/{}",
&common.server, &common.owner, &common.repo, release.id,
)
.as_str(),
)
.query(&[("name", fname)])
.multipart(form)
.send()
.await?;
Ok(())
}

View File

@ -1,212 +0,0 @@
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Author {
pub id: i64,
pub login: String,
pub full_name: String,
pub email: String,
pub avatar_url: String,
pub language: String,
pub is_admin: bool,
pub last_login: String,
pub created: String,
pub username: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Release {
pub id: i64,
pub tag_name: String,
pub target_commitish: String,
pub name: String,
pub body: String,
pub url: String,
pub tarball_url: String,
pub zipball_url: String,
pub draft: bool,
pub prerelease: bool,
pub created_at: String,
pub published_at: String,
pub author: Author,
pub assets: Vec<Attachment>,
}
use cli_table::{Cell, Row};
impl Release {
pub fn row(&self) -> Row {
Row::new(vec![
Cell::new(&format!("{}", self.id), Default::default()),
Cell::new(&self.tag_name, Default::default()),
Cell::new(&self.created_at, Default::default()),
Cell::new(&self.target_commitish, Default::default()),
Cell::new(&self.author.username, Default::default()),
Cell::new(&self.name, Default::default()),
])
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateRelease {
pub body: String,
pub draft: bool,
pub name: String,
pub prerelease: bool,
pub tag_name: String,
pub target_commitish: String,
}
pub(crate) async fn get_release_by_tag(
cli: &reqwest::Client,
server: &String,
owner: &String,
repo: &String,
tag: &String,
) -> Result<Release> {
let releases: Vec<Release> = cli
.get(&format!(
"{}/api/v1/repos/{}/{}/releases",
server, owner, repo
))
.send()
.await?
.json()
.await?;
let mut release: Option<Release> = None;
for rls in releases {
if *tag == rls.tag_name {
release = Some(rls);
}
}
if release.is_none() {
return Err(anyhow!("tag {} not found", tag));
}
Ok(release.unwrap())
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attachment {
pub id: i64,
pub name: String,
pub size: i64,
pub download_count: i64,
pub created_at: String,
pub uuid: String,
pub browser_download_url: String,
}
impl Attachment {
pub fn row(&self) -> Row {
let size = {
let bytes = byte_unit::Byte::from_bytes(self.size as u128);
let unit = bytes.get_appropriate_unit(false);
unit.to_string()
};
Row::new(vec![
Cell::new(&self.name, Default::default()),
Cell::new(&size, Default::default()),
Cell::new(&self.browser_download_url, Default::default()),
])
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Repo {
pub allow_merge_commits: bool,
pub allow_rebase: bool,
pub allow_rebase_explicit: bool,
pub allow_squash_merge: bool,
pub archived: bool,
pub avatar_url: String,
pub clone_url: String,
pub created_at: String,
pub default_branch: String,
pub description: String,
pub empty: bool,
pub fork: bool,
pub forks_count: i64,
pub full_name: String,
pub has_issues: bool,
pub has_pull_requests: bool,
pub has_wiki: bool,
pub html_url: String,
pub id: i64,
pub ignore_whitespace_conflicts: bool,
pub mirror: bool,
pub name: String,
pub open_issues_count: i64,
pub open_pr_counter: i64,
pub original_url: String,
pub owner: Owner,
pub permissions: Permissions,
pub private: bool,
pub release_counter: i64,
pub size: i64,
pub ssh_url: String,
pub stars_count: i64,
pub template: bool,
pub updated_at: String,
pub watchers_count: i64,
pub website: String,
}
pub(crate) async fn get_repo(
cli: &reqwest::Client,
server: &String,
owner: &String,
repo: &String,
) -> Result<Repo> {
Ok(cli
.get(&format!("{}/api/v1/repos/{}/{}", server, owner, repo))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Owner {
pub avatar_url: String,
pub created: String,
pub email: String,
pub full_name: String,
pub id: i64,
pub is_admin: bool,
pub language: String,
pub last_login: String,
pub login: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Permissions {
pub admin: bool,
pub pull: bool,
pub push: bool,
}
pub(crate) async fn get_attachments_for_release(
cli: &reqwest::Client,
server: &String,
owner: &String,
repo: &String,
id: &i64,
) -> Result<Vec<Attachment>> {
let attachments: Vec<Attachment> = cli
.get(&format!(
"{}/api/v1/repos/{}/{}/releases/{}/assets",
server, owner, repo, id
))
.send()
.await?
.json()
.await?;
Ok(attachments)
}