Compare commits

..

7 Commits

Author SHA1 Message Date
02c62b7ba6 wip: add base64 encoding to impl, add custom errors to impl
Some checks failed
Rust / build (push) Failing after 7m58s
2024-08-30 08:17:53 -04:00
8e85ceebaf fixed up base64 encoding 2024-08-30 08:16:58 -04:00
aa80bc0cc7 added dir to gitignore, removed fil 2024-08-30 08:15:59 -04:00
fe1334d97f added custom error handling 2024-08-30 08:15:18 -04:00
808c6a526c added base64 encoding option to basic auth
Some checks failed
Rust / build (push) Failing after 8m11s
2024-08-26 19:45:26 -04:00
982452cb5b Merge remote-tracking branch 'origin/master' into 0.3.0 2024-08-25 10:06:46 -05:00
51856d1108 changes
All checks were successful
Rust / build (push) Successful in 9m13s
2024-08-17 08:54:49 -04:00
10 changed files with 1950 additions and 113 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
/target /target
.vscode/ .vscode/
config.json config.json
Cargo.lock .idea/

1459
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "gt" name = "gt"
version = "0.2.0" version = "0.3.0"
authors = ["Wyatt J. Miller <wjmiller2016@gmail.com>"] authors = ["Wyatt J. Miller <wjmiller2016@gmail.com>"]
edition = "2018" edition = "2018"
description = "A Gitea CLI client" description = "A Gitea CLI client"
@ -11,6 +11,7 @@ license-file = "LICENSE"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
base64 = "0.22.1"
clap = "2.33.1" clap = "2.33.1"
colored = "2.0.0" colored = "2.0.0"
config = "0.11.0" config = "0.11.0"

43
src/error.rs Normal file
View File

@ -0,0 +1,43 @@
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum ErrorKind {
BadRequest(String),
Conflict(String),
ForbiddenRequest(String),
NotFound(String),
UnprocessiableRequest(String),
JsonError(String),
Other,
}
impl Error for ErrorKind {}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ErrorKind::BadRequest(message) => {
write!(f, "Client error - please try again! {}", message)
}
ErrorKind::Conflict(message) => {
write!(f, "Client error - task is already underway! {}", message)
}
ErrorKind::ForbiddenRequest(message) => write!(
f,
"Client error - unauthorized. Please try again! {}",
message
),
ErrorKind::NotFound(message) => write!(f, "Client error - not found! {}", message),
ErrorKind::UnprocessiableRequest(message) => write!(
f,
"Client error - the request can't be processed. Please try again! {}",
message
),
ErrorKind::JsonError(message) => {
write!(f, "Client error - can't parse command! {}", message)
}
ErrorKind::Other => write!(f, "Other error that I did not anticipate"),
}
}
}

View File

@ -2,11 +2,12 @@ use std::collections::HashMap;
use colored::*; use colored::*;
use reqwest::blocking::Client; use reqwest::blocking::Client;
use reqwest::{StatusCode, Response}; use reqwest::{Response, StatusCode};
use serde_derive::{Serialize, Deserialize}; use serde_derive::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use crate::request::{Request, Authentication, AuthenticationType}; use crate::error;
use crate::request::{AuthenticationType, Request};
use crate::util; use crate::util;
pub struct Issue; pub struct Issue;
@ -44,6 +45,12 @@ pub struct IssueResponse {
pub repository: Repository, pub repository: Repository,
} }
#[derive(Deserialize)]
pub struct IssueErrorResponse {
pub message: String,
pub url: String,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct User { pub struct User {
pub id: i64, pub id: i64,
@ -67,24 +74,12 @@ pub struct Repository {
pub full_name: String, pub full_name: String,
} }
// trait IssueRequest {
// fn client() -> &'static Client;
// fn url_request_decision() -> String;
// fn arg_value_decision() -> Vec<&'static str>;
// }
// impl IssueRequest for Issue {
// fn client() -> &'static Client {
// return
// }
// }
impl Issue { impl Issue {
pub fn new() -> Issue { pub fn new() -> Issue {
Issue {} Issue {}
} }
pub fn create_issue(&self, request: &Request) { pub fn create_issue(&self, request: &Request) -> Result<(), error::ErrorKind> {
let issue_title: String; let issue_title: String;
let issue_description: String; let issue_description: String;
let _issue_assignee: Option<String>; let _issue_assignee: Option<String>;
@ -119,51 +114,60 @@ impl Issue {
let response = client.post(&url).json(&map).send(); let response = client.post(&url).json(&map).send();
match response { match response {
Ok(repo) => { Ok(repo) => match repo.status() {
match repo.status() { StatusCode::CREATED => println!("{}", "Issue successfully created!".green()),
StatusCode::CREATED => println!("{}", "Issue successfully created!".green()), StatusCode::FORBIDDEN => println!("{}", "Issue creation forbidden!".red()),
StatusCode::FORBIDDEN => println!("{}", "Issue creation forbidden!".red()), StatusCode::UNPROCESSABLE_ENTITY => {
StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Issue input validation failed!".red()), println!("{}", "Issue input validation failed!".red())
_ => println!(),
} }
_ => println!(),
}, },
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
} }
} }
pub fn list_issue(&self, request: &Request) { pub fn list_issue(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let url = self.url_request_decision(&request); let url = self.url_request_decision(&request);
let response = client.get(&url).send(); let response = client.get(&url).send();
// TODO: fix this to match context // TODO: fix this to match context
match response { match response {
Ok(repo) => { Ok(repo) => match repo.status() {
match repo.status() { StatusCode::CREATED => {
StatusCode::CREATED => println!("{}", "Issue successfully created!".green()), println!("{}", "Issue successfully created!".green());
StatusCode::FORBIDDEN => println!("{}", "Issue creation forbidden!".red()), Ok(())
StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Issue input validation failed!".red()),
_ => println!(),
} }
StatusCode::FORBIDDEN => {
let deserialized = repo.json().unwrap();
Err(error::ErrorKind::ForbiddenRequest(deserialized.message))
}
StatusCode::UNPROCESSABLE_ENTITY => {
let deserialized = repo.json().unwrap();
Err(error::ErrorKind::UnprocessiableRequest(
deserialized.message,
))
}
_ => Err(error::ErrorKind::Other),
}, },
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
} }
} }
pub fn search_issue(&self, request: &Request) { pub fn search_issue(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let url = self.url_request_decision(&request); let url = self.url_request_decision(&request);
let response = client.get(&url).send(); let response = client.get(&url).send();
// TODO: fix this to match context // TODO: fix this to match context
match response { match response {
Ok(repo) => { Ok(repo) => match repo.status() {
match repo.status() { StatusCode::CREATED => println!("{}", "Issue successfully created!".green()),
StatusCode::CREATED => println!("{}", "Issue successfully created!".green()), StatusCode::FORBIDDEN => println!("{}", "Issue creation forbidden!".red()),
StatusCode::FORBIDDEN => println!("{}", "Issue creation forbidden!".red()), StatusCode::UNPROCESSABLE_ENTITY => {
StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Issue input validation failed!".red()), println!("{}", "Issue input validation failed!".red())
_ => println!(),
} }
_ => println!(),
}, },
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
} }
@ -190,7 +194,7 @@ impl Issue {
repo = arg_value[1], repo = arg_value[1],
api_token = request.authentication.credentials.1.as_ref().unwrap() api_token = request.authentication.credentials.1.as_ref().unwrap()
) )
}, }
AuthenticationType::BasicAuth => { AuthenticationType::BasicAuth => {
format!( format!(
"{request}/repos/{owner}/{repo}/issues?token={api_token}", "{request}/repos/{owner}/{repo}/issues?token={api_token}",
@ -199,10 +203,9 @@ impl Issue {
repo = arg_value[1], repo = arg_value[1],
api_token = request.authentication.credentials.1.as_ref().unwrap() api_token = request.authentication.credentials.1.as_ref().unwrap()
) )
}, }
// this case _shouldn't_ happen but ya know // this case _shouldn't_ happen but ya know
AuthenticationType::None => panic!("idk what happened man you wrote the code 🤷") AuthenticationType::None => panic!("idk what happened man you wrote the code 🤷"),
} }
} }
} }

View File

@ -5,6 +5,7 @@
mod arg; mod arg;
mod config; mod config;
mod error;
mod generate; mod generate;
mod issue; mod issue;
mod pr; mod pr;
@ -28,7 +29,7 @@ fn main() {
} }
let auth = request::Authentication::new(&config); let auth = request::Authentication::new(&config);
let request = auth.request_chooser(config.clone(), matches); let request = auth.request_chooser(config.clone(), &matches); // TODO: get rid of the clone
match request.arg_value.subcommand() { match request.arg_value.subcommand() {
("", None) => println!("No subcommand was given!"), ("", None) => println!("No subcommand was given!"),
@ -76,6 +77,26 @@ fn main() {
issue.create_issue(&request); issue.create_issue(&request);
} }
} }
("user", Some(user_matches)) => {
let user = user::User::new();
// TODO: match expression should be here
if user_matches.is_present("create") {
user.create_user(&request);
}
if user_matches.is_present("list") {
user.list_user(&request);
}
if user_matches.is_present("search") {
user.search_user(&request);
}
if user_matches.is_present("delete") {
user.delete_user(&request);
}
}
_ => println!("Huh?"), _ => println!("Huh?"),
} }
} }

View File

@ -1,11 +1,16 @@
use std::{collections::HashMap, path::{Path, self}, file}; use std::{collections::HashMap, path::Path};
use colored::*; use colored::*;
use git2::{Repository as Repo, build::RepoBuilder, Credentials, Cred, CredentialType, RemoteCallbacks, FetchOptions}; use git2::{build::RepoBuilder, Cred, CredentialType, RemoteCallbacks};
use reqwest::StatusCode; use reqwest::StatusCode;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use crate::{request::Request, config::Configuration}; use crate::{
config::Configuration,
error,
request::Request,
util::{self, ErrorKind},
};
pub struct Repository; pub struct Repository;
@ -58,6 +63,12 @@ pub struct RepositoryResponse {
pub website: String, pub website: String,
} }
#[derive(Deserialize)]
pub struct RepositoryErrorResponse {
pub message: String,
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ExternalTracker { pub struct ExternalTracker {
pub external_tracker_format: String, pub external_tracker_format: String,
@ -102,7 +113,7 @@ impl Repository {
Repository {} Repository {}
} }
pub fn create_repo(&self, request: &Request) { pub fn create_repo(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let arg_value = request let arg_value = request
.arg_value .arg_value
@ -127,24 +138,34 @@ impl Repository {
match response { match response {
Ok(repo) => match repo.status() { Ok(repo) => match repo.status() {
StatusCode::CREATED => { StatusCode::CREATED => {
// TODO: implement error handling for the deserialization
let deserialized: RepositoryResponse = repo.json().unwrap(); let deserialized: RepositoryResponse = repo.json().unwrap();
println!("{}", "Repository successfully created!".green()); println!("{}", "Repository successfully created!".green());
println!("\tRepository name: {:0}\n\tRepository owner: {:1}\n\tRepository description: {:2}", deserialized.name, deserialized.owner.unwrap().full_name, deserialized.description); println!("\tRepository name: {:0}\n\tRepository owner: {:1}\n\tRepository description: {:2}", deserialized.name, deserialized.owner.unwrap().full_name, deserialized.description);
Ok(())
}
StatusCode::BAD_REQUEST => {
// TODO: implement error handling for the deserialization
let deserialized: RepositoryErrorResponse = repo.json().unwrap();
Err(error::ErrorKind::BadRequest(deserialized.message))
}
StatusCode::CONFLICT => {
let deserialized: RepositoryErrorResponse = repo.json().unwrap();
Err(error::ErrorKind::Conflict(deserialized.message))
} }
StatusCode::CONFLICT => println!("{}", "Repository already exists!".red()),
StatusCode::UNPROCESSABLE_ENTITY => { StatusCode::UNPROCESSABLE_ENTITY => {
println!("{}", "Repository input validation failed!".red()) let deserialized: RepositoryErrorResponse = repo.json().unwrap();
Err(error::ErrorKind::UnprocessiableRequest(
deserialized.message,
))
} }
_ => println!( _ => Err(error::ErrorKind::Other),
"Repository creation failed! HTTP status code: {}",
repo.status().as_str()
),
}, },
Err(e) => panic!("{}", e), Err(_e) => Err(error::ErrorKind::Other),
} }
} }
pub fn delete_repo(&self, request: &Request) { pub fn delete_repo(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let arg_value: Vec<&str> = request let arg_value: Vec<&str> = request
.arg_value .arg_value
@ -166,8 +187,13 @@ impl Repository {
match response { match response {
Ok(repo) => match repo.status() { Ok(repo) => match repo.status() {
StatusCode::NO_CONTENT => println!("{}", "Respository successfully deleted!".green()), StatusCode::NO_CONTENT => {
StatusCode::FORBIDDEN => println!("{}", "Repository deletion forbidden!".red()), println!("{}", "Respository successfully deleted!".green())
}
StatusCode::FORBIDDEN => println!(
"{}",
"Unable to authorize deletion. Please report this to the webmaster!".red()
),
_ => println!( _ => println!(
"Repository deletion failed! Does the repository exist? HTTP status code: {}", "Repository deletion failed! Does the repository exist? HTTP status code: {}",
repo.status().as_str() repo.status().as_str()
@ -177,7 +203,7 @@ impl Repository {
} }
} }
pub fn fork_repo(&self, request: &Request) { pub fn fork_repo(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let arg_item: Vec<&str> = request let arg_item: Vec<&str> = request
.arg_value .arg_value
@ -191,6 +217,7 @@ impl Repository {
let mut map: HashMap<&str, &str> = HashMap::new(); let mut map: HashMap<&str, &str> = HashMap::new();
let user = request.authentication.credentials.0.as_ref().unwrap(); let user = request.authentication.credentials.0.as_ref().unwrap();
// TODO: use new url formatter
let url = format!( let url = format!(
"{request}/repos/{owner}/{repo}/forks?token={api_token}", "{request}/repos/{owner}/{repo}/forks?token={api_token}",
request = request.url.as_ref().unwrap(), request = request.url.as_ref().unwrap(),
@ -210,17 +237,27 @@ impl Repository {
println!("{}", "Repository forked successfully".green()); println!("{}", "Repository forked successfully".green());
println!("\tOriginal repository name: {:0?}\n\tOriginal repository owner: {:1?}\n\tForked repository name: {:2?}\n\tForked repository owner: {:3?}", deserialized.name, arg_item[0], deserialized.name, deserialized.owner.unwrap().full_name); println!("\tOriginal repository name: {:0?}\n\tOriginal repository owner: {:1?}\n\tForked repository name: {:2?}\n\tForked repository owner: {:3?}", deserialized.name, arg_item[0], deserialized.name, deserialized.owner.unwrap().full_name);
} }
StatusCode::INTERNAL_SERVER_ERROR => println!("{}", "Repository already forked!".red()), StatusCode::INTERNAL_SERVER_ERROR => {
StatusCode::FORBIDDEN => println!("{}", "Repository fork forbidden!".red()), println!("{}", "Repository already forked!".red())
StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Repository fork input validation failed!".red()), }
StatusCode::FORBIDDEN => println!(
"{}",
"Unable to authorize fork. Please report this to the webmaster!".red()
),
StatusCode::UNPROCESSABLE_ENTITY => {
println!("{}", "Repository fork input validation failed!".red())
}
StatusCode::NOT_FOUND => println!("{}", "Repository not found!"), StatusCode::NOT_FOUND => println!("{}", "Repository not found!"),
_ => println!("Repository creation failed! HTTP status code: {}", repo.status().as_str()), _ => println!(
"Repository creation failed! HTTP status code: {}",
repo.status().as_str()
),
}, },
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
} }
} }
pub fn search_repo(&self, request: &Request) { pub fn search_repo(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let arg_value = request let arg_value = request
.arg_value .arg_value
@ -229,6 +266,7 @@ impl Repository {
.unwrap() .unwrap()
.value_of("search") .value_of("search")
.unwrap(); .unwrap();
// TODO: use new url formatter
let url = format!( let url = format!(
"{request}/repos/search?q={query}&token={api_token}", "{request}/repos/search?q={query}&token={api_token}",
request = request.url.as_ref().unwrap(), request = request.url.as_ref().unwrap(),
@ -251,20 +289,28 @@ impl Repository {
println!("{}.\tRepository name: {:1}\n\tRepository owner: {:2}\n\tRepository description: {:3}\n", i + 1, data.name, data.owner.as_ref().unwrap().full_name, data.description) println!("{}.\tRepository name: {:1}\n\tRepository owner: {:2}\n\tRepository description: {:3}\n", i + 1, data.name, data.owner.as_ref().unwrap().full_name, data.description)
} }
println!("Total number of repositories indexed: {}", deserialized.data.iter().count()) println!(
"Total number of repositories indexed: {}",
deserialized.data.iter().count()
)
} }
false => println!("{}", "Repository searched doesn't exist!".red()), false => println!("{}", "Repository searched doesn't exist!".red()),
} }
} }
StatusCode::NOT_FOUND => println!("{}", "Repository searched doesn't exist!".red()), StatusCode::NOT_FOUND => println!("{}", "Repository searched doesn't exist!".red()),
StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Repository input validation failed!".red()), StatusCode::UNPROCESSABLE_ENTITY => {
_ => println!("Repository search failed! HTTP status code: {}", repo.status().as_str()), println!("{}", "Repository input validation failed!".red())
}
_ => println!(
"Repository search failed! HTTP status code: {}",
repo.status().as_str()
),
}, },
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
} }
} }
pub fn list_repo(&self, request: &Request) { pub fn list_repo(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let url = format!( let url = format!(
"{request}/repos/search?token={api_token}", "{request}/repos/search?token={api_token}",
@ -292,8 +338,13 @@ impl Repository {
false => println!("{}", "The authenticated user doesn't have any repositories. Why not create one?".yellow()) false => println!("{}", "The authenticated user doesn't have any repositories. Why not create one?".yellow())
} }
} }
StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Repository input validation failed!".red()), StatusCode::UNPROCESSABLE_ENTITY => {
_ => println!("Repository search failed! HTTP status code: {}",repo.status().as_str()), println!("{}", "Repository input validation failed!".red())
}
_ => println!(
"Repository search failed! HTTP status code: {}",
repo.status().as_str()
),
}, },
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
} }
@ -302,7 +353,6 @@ impl Repository {
pub fn push_to_remote(&self, request: &Request, config: &Configuration) { pub fn push_to_remote(&self, request: &Request, config: &Configuration) {
// code to push to the remote server goes here // code to push to the remote server goes here
// this is based on the user, the remote, and the branch // this is based on the user, the remote, and the branch
} }
pub fn pull_from_remote(&self, request: &Request, config: &Configuration) { pub fn pull_from_remote(&self, request: &Request, config: &Configuration) {
@ -332,7 +382,7 @@ impl Repository {
let user = _user_from_url.unwrap_or("git"); let user = _user_from_url.unwrap_or("git");
if _allowed_types.contains(CredentialType::USERNAME) { if _allowed_types.contains(CredentialType::USERNAME) {
return Cred::username(user) return Cred::username(user);
} }
// if request.url_request() { // if request.url_request() {
@ -349,24 +399,31 @@ impl Repository {
// ) // )
// } // }
Cred::userpass_plaintext( Cred::userpass_plaintext(
request.authentication.credentials.0.as_ref().unwrap().as_str(), request
config.password.as_ref().unwrap().as_str() .authentication
.credentials
.0
.as_ref()
.unwrap()
.as_str(),
config.password.as_ref().unwrap().as_str(),
) )
}); });
builder.branch("master"); builder.branch("master");
match builder.clone(url.as_str(), Path::new(arg_value[1])) { match builder.clone(url.as_str(), Path::new(arg_value[1])) {
Ok(_r) => Ok(println!("Repository cloned successfully!")), Ok(_r) => Ok(println!("Repository cloned successfully!")),
Err(_e) => Err(println!("Repository clone failed!")), Err(_e) => Err(println!("Repository clone failed!")),
_ => panic!("agh") _ => panic!("agh"),
}; };
} }
pub fn add_to_staging(&self, request: &Request, config: Configuration) { pub fn add_to_staging(&self, request: &Request, config: Configuration) {}
} pub fn create_commit(&self, request: &Request, config: Configuration) {}
}
pub fn create_commit(&self, request: &Request, config: Configuration) {
impl Default for Repository {
fn default() -> Self {
Repository::new()
} }
} }

View File

@ -1,9 +1,18 @@
use std::{collections::HashMap, process}; use std::{collections::HashMap, process, string::String};
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use clap::ArgMatches; use clap::ArgMatches;
use reqwest::blocking::Client; use reqwest::blocking::Client;
use crate::config::Configuration; use crate::config::Configuration;
use crate::util;
pub enum RequestType {
Repository,
Issue,
PullRequest,
User,
}
pub enum AuthenticationType { pub enum AuthenticationType {
BasicAuth, BasicAuth,
@ -11,12 +20,14 @@ pub enum AuthenticationType {
None, None,
} }
#[derive()]
pub struct Request<'a> { pub struct Request<'a> {
pub client: Client, pub client: Client,
pub arg_value: ArgMatches<'a>, pub arg_value: &'a ArgMatches<'a>,
pub map: HashMap<String, String>, pub map: HashMap<String, String>,
pub url: Option<String>, pub url: Option<String>,
pub authentication: Authentication, pub authentication: Authentication,
pub request_type: RequestType,
} }
pub struct Authentication { pub struct Authentication {
@ -30,32 +41,49 @@ impl<'a> Request<'a> {
/// Public constructor for a request with a simple username and password /// Public constructor for a request with a simple username and password
pub fn with_basic_request( pub fn with_basic_request(
config: Configuration, config: Configuration,
arg: ArgMatches, arg: &'a ArgMatches,
auth: Authentication, auth: Authentication,
) -> Request { ) -> Request<'a> {
Request { Request {
client: Client::new(), client: Client::new(),
arg_value: arg, arg_value: &arg,
map: HashMap::new(), map: HashMap::new(),
url: Some(format!("{}{}", config.base_url, config.base_api)), url: Some(format!("{}{}", config.base_url, config.base_api)),
authentication: auth, authentication: auth,
request_type: util::get_request_type(&arg).unwrap(),
} }
} }
/// Public constructor for a request with an API token /// Public constructor for a request with an API token
pub fn with_api_request( pub fn with_api_request(
config: Configuration, config: Configuration,
arg: ArgMatches, arg: &'a ArgMatches,
auth: Authentication, auth: Authentication,
) -> Request { ) -> Request<'a> {
Request { Request {
client: Client::new(), client: Client::new(),
arg_value: arg, arg_value: &arg,
map: HashMap::new(), map: HashMap::new(),
url: Some(format!("{}{}", config.base_url, config.base_api)), url: Some(format!("{}{}", config.base_url, config.base_api)),
authentication: auth, authentication: auth,
request_type: util::get_request_type(&arg).unwrap(),
} }
} }
pub fn auth_type(&self) -> String {
let key: &String = self.authentication.credentials.1.as_ref().unwrap();
match self.authentication.auth_type {
AuthenticationType::BasicAuth => URL_SAFE.encode(key),
AuthenticationType::ApiToken => key.to_string(),
AuthenticationType::None => {
// TODO: make a custom error and error here instead of panicing
// this shouldn't happen, ever
panic!();
}
}
}
pub fn url_builder(&self) -> String {}
} }
impl Authentication { impl Authentication {
@ -114,14 +142,22 @@ impl Authentication {
/// Public method that based on the what kind of authentication is being used, it can /// Public method that based on the what kind of authentication is being used, it can
/// determine what kind of requesting method is going to be used. See the Request /// determine what kind of requesting method is going to be used. See the Request
/// structure for more details. /// structure for more details.
pub fn request_chooser(self, config: Configuration, arg: ArgMatches<'static>) -> Request { pub fn request_chooser<'a>(
self,
config: Configuration,
arg: &'a ArgMatches<'static>,
) -> Request<'a> {
if let true = self.api_token { if let true = self.api_token {
Request::with_api_request(config, arg.to_owned(), self) Request::with_api_request(config, &arg, self)
} else { } else {
match self.basic { match self.basic {
true => Request::with_basic_request(config, arg.to_owned(), self), true => Request::with_basic_request(config, &arg, self),
false => panic!(), false => panic!(),
} }
} }
} }
} }
impl Default for Authentication {
fn default() -> Self {}
}

View File

@ -1,11 +1,18 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{request::Request, util}; use crate::{
error,
request::Request,
util::{self, ErrorKind},
};
use colored::*;
use reqwest::StatusCode;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
pub struct User; pub struct User;
pub struct MutlipleUsers { #[derive(Deserialize)]
pub struct MultipleUsers {
pub data: Vec<UserResponse>, pub data: Vec<UserResponse>,
pub ok: bool, pub ok: bool,
} }
@ -29,33 +36,180 @@ pub struct UserResponse {
pub restricted: bool, pub restricted: bool,
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserErrorResponse {
pub message: String,
pub url: String,
}
impl User { impl User {
pub fn new() -> User { pub fn new() -> User {
User {} User {}
} }
pub fn create_user(request: &Request) { pub fn create_user(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client; let client = &request.client;
let arg_value = &request let mut user_create_input: HashMap<String, String> = HashMap::new();
let url = format!(
"{request}/admin/users?token={api_token}",
request = request.url.as_ref().unwrap(),
api_token = request.authentication.credentials.1.as_ref().unwrap()
);
let username = util::get_input(String::from("Please enter a username:"));
let email = util::get_input(String::from("Please enter a email address:"));
let password = util::get_input(String::from("Please enter a password:"));
user_create_input.insert("login".to_string(), username);
user_create_input.insert("email".to_string(), email);
user_create_input.insert("password".to_string(), password);
let response = client.post(url.as_str()).json(&user_create_input).send();
// TODO: match return type
match response {
Ok(repo) => match repo.status() {
StatusCode::CREATED => {
let deserialized: UserResponse = repo.json().unwrap();
println!("{}", "User successfully created!".green());
println!(
"\tFull Name: {:0}\n\tUsername: {:1}\n\tEmail: {:2}\n",
deserialized.full_name, deserialized.login, deserialized.email
);
Ok(())
}
StatusCode::BAD_REQUEST => {
let deserialized: UserErrorResponse = repo.json().unwrap();
Err(error::ErrorKind::BadRequest(deserialized.message))
}
StatusCode::FORBIDDEN => {
let deserialized: UserErrorResponse = repo.json().unwrap();
Err(error::ErrorKind::ForbiddenRequest(deserialized.message))
}
_ => Err(error::ErrorKind::Other),
},
Err(_e) => Err(error::ErrorKind::Other),
}
}
pub fn list_user(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client;
let url = format!(
"{request}/users?token={api_token}",
request = request.url.as_ref().unwrap(),
api_token = request.authentication.credentials.1.as_ref().unwrap(),
);
let response = client.get(url).send();
match response {
Ok(repo) => {
match repo.status() {
StatusCode::OK => {
let deserialized: MultipleUsers = repo.json().unwrap();
match deserialized.data.len() != 0 {
true => {
println!("{}", "List of users found:");
for (i, data) in deserialized.data.iter().enumerate() {
println!("{}.\nUsername: {:1}\n\tEmail address: {:2}\n\tAdmin?: {:3}", i + 1, data.login, data.email, data.is_admin);
}
println!(
"Total number of users indexed: {}",
deserialized.data.len()
);
Ok(())
}
false => {
println!("{}", "No users found :(");
Ok(())
}
}
}
StatusCode::BAD_REQUEST => {
let deserialized: UserErrorResponse = repo.json().unwrap(); // TODO: handle
Err(error::ErrorKind::BadRequest(deserialized.message))
}
StatusCode::FORBIDDEN => {
let deserialzed: UserErrorResponse = repo.json().unwrap(); // TODO: handle errs
Err(error::ErrorKind::ForbiddenRequest(deserialzed.message))
}
_ => Err(error::ErrorKind::Other),
}
}
Err(_e) => Err(error::ErrorKind::Other),
}
}
pub fn search_user(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client;
let arg_value = request
.arg_value .arg_value
.subcommand() .subcommand()
.1 .1
.unwrap() .unwrap()
.value_of("create") .value_of("search")
.unwrap(); .unwrap();
let mut user_create_input: HashMap<String, String> = HashMap::new(); let url = format!(
let username = util::get_input(String::from("Please enter a username:")); "{request}/users?q={query}&token={api_token}",
let email = util::get_input(String::from("Please enter a email address:")); request = request.url.as_ref().unwrap(),
let password = util::get_input(String::from("Please enter a password:")); query = arg_value,
user_create_input.insert("login".to_string(), username); api_token = request.authentication.credentials.1.as_ref().unwrap(),
user_create_input.insert("email".to_string(), email); );
user_create_input.insert("password".to_string(), password);
let response = client.get(url.as_str()).send();
match response {
Ok(repo) => match repo.status() {
StatusCode::OK => Ok(()),
_ => Err(error::ErrorKind::Other),
},
Err(_e) => Err(error::ErrorKind::Other),
}
} }
pub fn list_user() {} pub fn delete_user(&self, request: &Request) -> Result<(), error::ErrorKind> {
let client = &request.client;
let arg_value = request
.arg_value
.subcommand()
.1
.unwrap()
.value_of("search")
.unwrap();
pub fn search_user() {} let url = format!(
"{request}/admin/users/{username}",
request = request.url.as_ref().unwrap(),
username = arg_value,
);
let response = client.delete(url.as_str()).send();
pub fn delete_user() {} match response {
Ok(repo) => match repo.status() {
StatusCode::NO_CONTENT => {
println!("User successfully deleted!");
Ok(())
}
StatusCode::FORBIDDEN => {
let deserialized: UserErrorResponse = repo.json().unwrap();
Err(error::ErrorKind::ForbiddenRequest(deserialized.message))
}
_ => Err(error::ErrorKind::Other),
},
Err(_e) => Err(error::ErrorKind::Other),
}
}
pub fn authed_user(&self, request: &Request) {
let client = &request.client;
}
}
impl Default for User {
fn default() -> Self {
User::new()
}
} }

View File

@ -1,5 +1,17 @@
use std::io; use std::io;
use clap::{ArgMatches, Error};
use crate::request::RequestType;
pub enum ErrorKind {
BadRequest,
ForbiddenRequest,
NotFound,
UnprocessiableRequest,
JsonError,
}
// TODO: Can't get this function to properly work // TODO: Can't get this function to properly work
// Currently, I'm grabbing input and have the server tell me whether or // Currently, I'm grabbing input and have the server tell me whether or
// not input was validated. What I want to have happen is if there's // not input was validated. What I want to have happen is if there's
@ -24,3 +36,54 @@ pub fn get_input(question: String) -> String {
} }
result result
} }
/// When you get a bad response from your Gitea server (any HTTP response that has
/// a status code not 2xx), and given the kind of response a user receives, the
/// appropriate message should be returned
pub fn bad_response_message(message: &String, error_kind: ErrorKind) -> String {
let final_message: String;
match error_kind {
ErrorKind::BadRequest => {
final_message = format!(
"Client error - please try again!\nError message: {}",
message
);
}
ErrorKind::ForbiddenRequest => {
final_message = format!(
"Client error - unauthorized. Please try again!\nError message: {}",
message
);
}
ErrorKind::NotFound => {
final_message = format!("Client error - not found!\nError message: {}", message);
}
ErrorKind::UnprocessiableRequest => {
final_message = format!("Client error - the request can't be processed. Please try again!\nError message: {}", message);
}
ErrorKind::JsonError => {
final_message = format!(
"Client error - can't parse command!\nError message: {}",
message
);
}
}
String::from(final_message)
}
/// Based on a subcommand that is passed, return the appropriate request
/// type
pub fn get_request_type(args: &ArgMatches) -> Result<RequestType, clap::Error> {
let request_type: &str = args.subcommand().0;
match request_type {
"repo" => Ok(RequestType::Repository),
"issue" => Ok(RequestType::Issue),
"pr" => Ok(RequestType::PullRequest),
"user" => Ok(RequestType::User),
_ => Err(clap::Error::with_description(
"Unknown or invalid command",
clap::ErrorKind::InvalidSubcommand,
)),
}
}