Cleanup some client structure
This commit is contained in:
parent
b0078dd224
commit
1bcdcda955
8 changed files with 176 additions and 61 deletions
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -1,10 +1,11 @@
|
|||
# cargo build output
|
||||
/target
|
||||
|
||||
# direnv cache
|
||||
/.direnv
|
||||
|
||||
# pre-commit config (setup by nix flake)
|
||||
/.pre-commit-config.yaml
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
# data
|
||||
/data
|
||||
|
|
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -103,6 +103,12 @@ version = "3.15.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.5.0"
|
||||
|
@ -176,6 +182,30 @@ version = "0.8.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
|
@ -253,6 +283,16 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.30"
|
||||
|
@ -342,6 +382,15 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
|
@ -1191,6 +1240,22 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sled"
|
||||
version = "0.34.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"fs2",
|
||||
"fxhash",
|
||||
"libc",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.1"
|
||||
|
@ -1266,6 +1331,7 @@ dependencies = [
|
|||
"reqwest-tracing",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sled",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
|
|
@ -14,6 +14,7 @@ reqwest-retry = "0.4.0"
|
|||
reqwest-tracing = "0.4.8"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
sled = "0.34.7"
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
|
|
|
@ -1,13 +1,68 @@
|
|||
use reqwest::Client;
|
||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||
use crate::result::Result;
|
||||
use reqwest::{Client as RClient, Method, Url};
|
||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||
use reqwest_tracing::TracingMiddleware;
|
||||
use serde::de;
|
||||
use tracing::debug;
|
||||
|
||||
pub fn wrap_with_middleware(client: Client) -> ClientWithMiddleware {
|
||||
pub struct Client {
|
||||
client: ClientWithMiddleware,
|
||||
base_url: Url,
|
||||
}
|
||||
|
||||
pub trait ResourceRequest {
|
||||
async fn res<T: de::DeserializeOwned>(self) -> Result<T>;
|
||||
}
|
||||
|
||||
impl ResourceRequest for RequestBuilder {
|
||||
async fn res<T: de::DeserializeOwned>(self) -> Result<T> {
|
||||
/*
|
||||
let body = self.send().await?.text().await?;
|
||||
debug!(body);
|
||||
serde_json::from_str(&body).map_err(|e| e.into())
|
||||
*/
|
||||
|
||||
self.send().await?.json().await.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn try_new(client: RClient, base_url: &str) -> Result<Self> {
|
||||
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
|
||||
|
||||
ClientBuilder::new(client)
|
||||
let client = ClientBuilder::new(client)
|
||||
.with(TracingMiddleware::default())
|
||||
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
|
||||
.build()
|
||||
.build();
|
||||
|
||||
// force trailing slash
|
||||
let mut base_url = Url::parse(base_url)?;
|
||||
base_url.set_path(&format!("{}{}", base_url.path().trim_end_matches('/'), "/"));
|
||||
|
||||
Ok(Self { client, base_url })
|
||||
}
|
||||
|
||||
/// Takes a path segment and properly joins it relative to `base_url`
|
||||
pub fn url(&self, path: &str) -> Result<Url> {
|
||||
self.base_url
|
||||
.join(path.trim_start_matches('/'))
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn build(&self, method: Method, rel_url_path: &str) -> Result<RequestBuilder> {
|
||||
Ok(self.client.request(method, self.url(rel_url_path)?))
|
||||
}
|
||||
|
||||
pub async fn request<T: de::DeserializeOwned>(
|
||||
&self,
|
||||
method: Method,
|
||||
rel_url_path: &str,
|
||||
) -> Result<T> {
|
||||
self.build(method, rel_url_path)?.res().await
|
||||
}
|
||||
|
||||
pub async fn get<T: de::DeserializeOwned>(&self, rel_url_path: &str) -> Result<T> {
|
||||
self.request::<T>(Method::GET, rel_url_path).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use crate::result::Result;
|
||||
use crate::{client::Client, result::Result};
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderValue},
|
||||
Client, Url,
|
||||
Client as RClient,
|
||||
};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct GitLab {
|
||||
url: Url,
|
||||
client: ClientWithMiddleware,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -25,24 +24,19 @@ pub struct User {
|
|||
|
||||
impl GitLab {
|
||||
pub fn try_new(url: &str, token: &str) -> Result<Self> {
|
||||
let url = Url::parse(&format!("{}{}", url.trim_end_matches('/'), "/"))?;
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("PRIVATE-TOKEN", HeaderValue::from_str(token)?);
|
||||
headers.insert("content-type", HeaderValue::from_str("application/json")?);
|
||||
headers.insert("accepts", HeaderValue::from_str("application/json")?);
|
||||
|
||||
let base_client = Client::builder().default_headers(headers).build()?;
|
||||
let client = crate::client::wrap_with_middleware(base_client);
|
||||
let base_client = RClient::builder().default_headers(headers).build()?;
|
||||
|
||||
Ok(Self { url, client })
|
||||
}
|
||||
let client = Client::try_new(base_client, url)?;
|
||||
|
||||
pub fn url(&self, path: &str) -> Result<Url> {
|
||||
Ok(self.url.join(path.trim_start_matches('/'))?)
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub async fn me(&self) -> Result<User> {
|
||||
let res = self.client.get(self.url("/user")?).send().await?;
|
||||
Ok(res.json().await?)
|
||||
self.client.get("/user").await
|
||||
}
|
||||
}
|
||||
|
|
47
src/jira.rs
47
src/jira.rs
|
@ -1,16 +1,18 @@
|
|||
use crate::result::Result;
|
||||
use crate::{
|
||||
client::{Client, ResourceRequest},
|
||||
result::Result,
|
||||
};
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderValue},
|
||||
Client, Url,
|
||||
Client as RClient, Method,
|
||||
};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
|
||||
use serde::Deserialize;
|
||||
use tokio::spawn;
|
||||
use tracing::debug;
|
||||
|
||||
pub struct Jira {
|
||||
url: Url,
|
||||
client: ClientWithMiddleware,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -43,7 +45,6 @@ pub struct IssueSearch {
|
|||
|
||||
impl Jira {
|
||||
pub fn try_new(url: &str, token: &str) -> Result<Self> {
|
||||
let url = Url::parse(&format!("{}{}", url.trim_end_matches('/'), "/"))?;
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
|
@ -51,39 +52,27 @@ impl Jira {
|
|||
);
|
||||
headers.insert("content-type", HeaderValue::from_str("application/json")?);
|
||||
headers.insert("accepts", HeaderValue::from_str("application/json")?);
|
||||
let base_client = RClient::builder().default_headers(headers).build()?;
|
||||
|
||||
let base_client = Client::builder().default_headers(headers).build()?;
|
||||
let client = crate::client::wrap_with_middleware(base_client);
|
||||
let client = Client::try_new(base_client, url)?;
|
||||
|
||||
Ok(Self { url, client })
|
||||
}
|
||||
|
||||
pub fn url(&self, path: &str) -> Result<Url> {
|
||||
Ok(self.url.join(path.trim_start_matches('/'))?)
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub async fn me(&self) -> Result<User> {
|
||||
let res = self
|
||||
.client
|
||||
.get(self.url("/rest/api/3/myself")?)
|
||||
.send()
|
||||
.await?;
|
||||
Ok(res.json().await?)
|
||||
self.client.get("/rest/api/3/myself").await
|
||||
}
|
||||
|
||||
pub async fn jql(&self, jql: &str) -> Result<Vec<Issue>> {
|
||||
debug!("Fetching issues for jql {}", jql);
|
||||
let mut results = self
|
||||
let mut results: IssueSearch = self
|
||||
.client
|
||||
.get(self.url("/rest/api/3/search")?)
|
||||
.build(Method::GET, "/rest/api/3/search")?
|
||||
.query(&[("jql", jql)])
|
||||
.send()
|
||||
.await?
|
||||
.json::<IssueSearch>()
|
||||
.res()
|
||||
.await?;
|
||||
|
||||
// TODO: parallelize after first page
|
||||
// TODO: are there rate limits to be concerned about
|
||||
// TODO: are there rate limits to be concerned about?
|
||||
|
||||
let mut issues = Vec::with_capacity(results.total);
|
||||
issues.append(&mut results.issues);
|
||||
|
@ -95,14 +84,14 @@ impl Jira {
|
|||
debug!("Fetching issues for jql {} (page {})", jql, i);
|
||||
futures.push(spawn(
|
||||
self.client
|
||||
.get(self.url("/rest/api/3/search")?)
|
||||
.build(Method::GET, "/rest/api/3/search")?
|
||||
.query(&[("jql", jql), ("startAt", &start_at.to_string())])
|
||||
.send(),
|
||||
.res::<IssueSearch>(),
|
||||
));
|
||||
}
|
||||
|
||||
for task in futures {
|
||||
let mut result = task.await.unwrap()?.json::<IssueSearch>().await?;
|
||||
let mut result = task.await??;
|
||||
issues.append(&mut result.issues);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use tasks::Tasks;
|
|||
use tracing::{error, info};
|
||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
||||
|
||||
#[allow(dead_code)]
|
||||
const ANSI_CLEAR: &'static str = "\x1b[2J\x1b[1;1H";
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -31,7 +32,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
async fn run() -> Result<()> {
|
||||
print!("{ANSI_CLEAR}");
|
||||
// print!("{ANSI_CLEAR}");
|
||||
let tasks = Tasks::try_new()?;
|
||||
let gitlab_user = tasks.gitlab.me().await?;
|
||||
info!("{gitlab_user:#?}");
|
||||
|
|
12
src/tasks.rs
12
src/tasks.rs
|
@ -1,5 +1,7 @@
|
|||
use std::{collections::HashSet, env, process::Command};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{gitlab::GitLab, jira::Jira, result::Result};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -22,6 +24,8 @@ pub struct Task {
|
|||
pub struct Tasks {
|
||||
pub gitlab: GitLab,
|
||||
pub jira: Jira,
|
||||
|
||||
db: sled::Db,
|
||||
}
|
||||
|
||||
impl Tasks {
|
||||
|
@ -44,10 +48,14 @@ impl Tasks {
|
|||
})?;
|
||||
let jira = Jira::try_new("https://billcom.atlassian.net", &jira_token)?;
|
||||
|
||||
Ok(Self { gitlab, jira })
|
||||
let db = sled::open("data/tasks")?;
|
||||
|
||||
Ok(Self { gitlab, jira, db })
|
||||
}
|
||||
|
||||
pub async fn all(&self) -> Result<Vec<Task>> {}
|
||||
pub async fn all(&self) -> Result<Vec<Task>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// fetches jira issues and compares to known local tasks
|
||||
/// for use when sync'ing local tasks to remote state (jira, gitlab)
|
||||
|
|
Loading…
Reference in a new issue