diff --git a/Cargo.lock b/Cargo.lock index 949d11e..90459d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,7 +339,7 @@ dependencies = [ [[package]] name = "cup" -version = "2.4.0" +version = "3.0.0" dependencies = [ "bollard", "chrono", @@ -349,7 +349,6 @@ dependencies = [ "http-link", "indicatif", "itertools", - "json", "liquid", "once_cell", "regex", @@ -848,12 +847,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - [[package]] name = "kstring" version = "2.0.2" @@ -981,16 +974,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -1037,12 +1020,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parking_lot" version = "0.11.2" @@ -1556,15 +1533,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1674,16 +1642,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "time" version = "0.3.36" @@ -1839,32 +1797,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", ] [[package]] @@ -1941,12 +1873,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "version_check" version = "0.9.4" @@ -2347,8 +2273,6 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tracing", - "tracing-subscriber", "xitca-http", "xitca-server", "xitca-service", diff --git a/Cargo.toml b/Cargo.toml index 107c3cd..0f5502a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "cup" -version = "2.4.0" +version = "3.0.0" edition = "2021" [dependencies] clap = { version = "4.5.7", features = ["derive"] } indicatif = { version = "0.17.8", optional = true } -tokio = { version = "1.38.0", features = ["macros"] } -xitca-web = { version = "0.5.0", optional = true, features = ["logger"] } +tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] } +xitca-web = { version = "0.5.0", optional = true } liquid = { version = "0.26.6", optional = true } bollard = "0.16.1" once_cell = "1.19.0" @@ -15,7 +15,6 @@ http-auth = { version = "0.1.9", default-features = false, features = [] } termsize = { version = "0.1.8", optional = true } regex = { version = "1.10.5", default-features = false, features = ["perf"] } chrono = { version = "0.4.38", default-features = false, features = ["std", "alloc", "clock"], optional = true } -json = "0.12.4" reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls"] } futures = "0.3.30" reqwest-retry = "0.6.1" diff --git a/README.md b/README.md index ff81d77..97f6a84 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ _If you like this project and/or use Cup, please consider starring the project - Supports most registries, including Docker Hub, ghcr.io, Quay, lscr.io and even Gitea (or derivatives) - Doesn't exhaust any rate limits. This is the original reason I created Cup. It was inspired by [What's up docker?](https://github.com/getwud/wud) which would always use it up. - Beautiful CLI and web interface for checking on your containers any time. -- The binary is tiny! At the time of writing it's just 5.2 MB. No more pulling 100+ MB docker images for a such a simple program. +- The binary is tiny! At the time of writing it's just 5.1 MB. No more pulling 100+ MB docker images for a such a simple program. - JSON output for both the CLI and web interface so you can connect Cup to integrations. It's easy to parse and makes webhooks and pretty dashboards simple to set up! ## Documentation 📘 diff --git a/docs/components/pages/Home.tsx b/docs/components/pages/Home.tsx index 0cc4ea6..7c69fde 100644 --- a/docs/components/pages/Home.tsx +++ b/docs/components/pages/Home.tsx @@ -106,7 +106,7 @@ export function Home() {
{ - let start = timestamp(); + let start = SystemTime::now(); match *raw || config.debug { true => { let updates = get_updates(references, &config).await; @@ -91,9 +91,8 @@ async fn main() { let spinner = Spinner::new(); let updates = get_updates(references, &config).await; spinner.succeed(); - let end = timestamp(); print_updates(&updates, icons); - info!("✨ Checked {} images in {}ms", updates.len(), end - start); + info!("✨ Checked {} images in {}ms", updates.len(), start.elapsed().unwrap().as_millis()); } }; } diff --git a/src/registry.rs b/src/registry.rs index d713354..ea4286e 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,3 +1,5 @@ +use std::time::SystemTime; + use itertools::Itertools; use crate::{ @@ -10,10 +12,9 @@ use crate::{ }, utils::{ link::parse_link, - misc::timestamp, request::{ get_protocol, get_response_body, parse_json, parse_www_authenticate, to_bearer_string, - }, + }, time::{elapsed, now}, }, }; @@ -50,7 +51,7 @@ pub async fn get_latest_digest( config.debug, "Checking for digest update to {}", image.reference ); - let start = timestamp(); + let start = SystemTime::now(); let protocol = get_protocol(&image.registry, &config.registries); let url = format!( "{}://{}/v2/{}/manifests/{}", @@ -60,7 +61,7 @@ pub async fn get_latest_digest( let headers = vec![("Accept", Some("application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.index.v1+json")), ("Authorization", authorization.as_deref())]; let response = client.head(&url, headers).await; - let time = timestamp() - start; + let time = start.elapsed().unwrap().as_millis() as u32; debug!( config.debug, "Checked for digest update to {} in {}ms", image.reference, time @@ -112,7 +113,7 @@ pub async fn get_token( Ok(response) => parse_json(&get_response_body(response).await), Err(_) => error!("GET {}: Request failed!", url), }; - response_json["token"].to_string() + response_json["token"].as_str().unwrap().to_string() } pub async fn get_latest_tag( @@ -126,7 +127,7 @@ pub async fn get_latest_tag( config.debug, "Checking for tag update to {}", image.reference ); - let start = timestamp(); + let start = now(); let protocol = get_protocol(&image.registry, &config.registries); let url = format!( "{}://{}/v2/{}/tags/list", @@ -148,25 +149,31 @@ pub async fn get_latest_tag( image.reference, tags.len() ); - let (new_tags, next) = - match get_extra_tags(&next_url.unwrap(), headers.clone(), base, &image.version_info.as_ref().unwrap().format_str, client).await { - Ok(t) => t, - Err(message) => { - return Image { - error: Some(message), - time_ms: image.time_ms + (timestamp() - start), - ..image.clone() - } + let (new_tags, next) = match get_extra_tags( + &next_url.unwrap(), + headers.clone(), + base, + &image.version_info.as_ref().unwrap().format_str, + client, + ) + .await + { + Ok(t) => t, + Err(message) => { + return Image { + error: Some(message), + time_ms: image.time_ms + elapsed(start), + ..image.clone() } - }; + } + }; tags.extend_from_slice(&new_tags); next_url = next; } let tag = tags.iter().max(); - let time = timestamp() - start; debug!( config.debug, - "Checked for tag update to {} in {}ms", image.reference, time + "Checked for tag update to {} in {}ms", image.reference, elapsed(start) ); match tag { Some(t) => { @@ -178,7 +185,7 @@ pub async fn get_latest_tag( latest_remote_tag: Some(t.clone()), ..image.version_info.as_ref().unwrap().clone() }), - time_ms: image.time_ms + time, + time_ms: image.time_ms + elapsed(start), ..image.clone() }, token, @@ -192,7 +199,7 @@ pub async fn get_latest_tag( latest_remote_tag: Some(t.clone()), ..image.version_info.as_ref().unwrap().clone() }), - time_ms: image.time_ms + time, + time_ms: image.time_ms + elapsed(start), ..image.clone() } } @@ -218,11 +225,14 @@ pub async fn get_extra_tags( .map(|link| parse_link(link.to_str().unwrap(), url)); let response_json = parse_json(&get_response_body(res).await); let result = response_json["tags"] - .members() - .filter_map(|tag| Version::from_tag(&tag.to_string())) + .as_array() + .unwrap() + .iter() + .filter_map(|tag| Version::from_tag(tag.as_str().unwrap())) .filter(|(tag, format_string)| match (base.minor, tag.minor) { (Some(_), Some(_)) | (None, None) => { - matches!((base.patch, tag.patch), (Some(_), Some(_)) | (None, None)) && format_str == *format_string + matches!((base.patch, tag.patch), (Some(_), Some(_)) | (None, None)) + && format_str == *format_string } _ => false, }) diff --git a/src/server.rs b/src/server.rs index 8d3f591..846fe3e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,16 +1,17 @@ use std::sync::Arc; use chrono::Local; -use json::JsonValue; use liquid::{object, Object, ValueView}; +use serde_json::Value; use tokio::sync::Mutex; use xitca_web::{ body::ResponseBody, + error::Error, handler::{handler_service, path::PathRef, state::StateRef}, - http::WebResponse, - middleware::Logger, + http::{StatusCode, WebResponse}, route::get, - App, + service::Service, + App, WebContext, }; use crate::{ @@ -20,8 +21,8 @@ use crate::{ structs::image::Image, utils::{ json::{to_full_json, to_simple_json}, - misc::timestamp, sort_update_vec::sort_image_vec, + time::{elapsed, now}, }, }; @@ -59,7 +60,7 @@ pub async fn serve(port: &u16, config: &Config) -> std::io::Result<()> { .at("/*", get(handler_service(_static))); } app_builder - .enclosed(Logger::new()) + .enclosed_fn(logger) .serve() .bind(format!("0.0.0.0:{}", port))? .run() @@ -105,18 +106,18 @@ async fn _static(data: StateRef<'_, Arc>>, path: PathRef<'_>) async fn api_simple(data: StateRef<'_, Arc>>) -> WebResponse { WebResponse::builder() .header("Content-Type", "application/json") - .body(ResponseBody::from(json::stringify( - data.lock().await.simple_json.clone(), - ))) + .body(ResponseBody::from( + data.lock().await.simple_json.clone().to_string(), + )) .unwrap() } async fn api_full(data: StateRef<'_, Arc>>) -> WebResponse { WebResponse::builder() .header("Content-Type", "application/json") - .body(ResponseBody::from(json::stringify( - data.lock().await.full_json.clone(), - ))) + .body(ResponseBody::from( + data.lock().await.full_json.clone().to_string(), + )) .unwrap() } @@ -128,8 +129,8 @@ async fn refresh(data: StateRef<'_, Arc>>) -> WebResponse { struct ServerData { template: String, raw_updates: Vec, - simple_json: JsonValue, - full_json: JsonValue, + simple_json: Value, + full_json: Value, config: Config, theme: &'static str, } @@ -139,8 +140,8 @@ impl ServerData { let mut s = Self { config: config.clone(), template: String::new(), - simple_json: JsonValue::Null, - full_json: JsonValue::Null, + simple_json: Value::Null, + full_json: Value::Null, raw_updates: Vec::new(), theme: "neutral", }; @@ -148,13 +149,16 @@ impl ServerData { s } async fn refresh(&mut self) { - let start = timestamp(); + let start = now(); if !self.raw_updates.is_empty() { info!("Refreshing data"); } let updates = sort_image_vec(&get_updates(&None, &self.config).await); - let end = timestamp(); - info!("✨ Checked {} images in {}ms", updates.len(), end - start); + info!( + "✨ Checked {} images in {}ms", + updates.len(), + elapsed(start) + ); self.raw_updates = updates; let template = liquid::ParserBuilder::with_stdlib() .build() @@ -173,16 +177,29 @@ impl ServerData { .to_rfc3339_opts(chrono::SecondsFormat::Secs, true) .to_string() .into(); - self.full_json["last_updated"] = last_updated - .to_rfc3339_opts(chrono::SecondsFormat::Secs, true) - .to_string() - .into(); + self.full_json["last_updated"] = self.simple_json["last_updated"].clone(); self.theme = match &self.config.theme { Theme::Default => "neutral", Theme::Blue => "gray", }; - let mut metrics = self.simple_json["metrics"].entries().map(|(key, value)| liquid::object!({ "name": key, "value": value.as_u16().unwrap()})).collect::>(); - metrics.sort_unstable_by(|a, b| {dbg!(a, b); SORT_ORDER.iter().position(|i| i == &a["name"].to_kstr().as_str()).unwrap().cmp(&SORT_ORDER.iter().position(|i| i == &b["name"].to_kstr().as_str()).unwrap())}); + let mut metrics = self.simple_json["metrics"] + .as_object() + .unwrap() + .iter() + .map(|(key, value)| liquid::object!({ "name": key, "value": value })) + .collect::>(); + metrics.sort_unstable_by(|a, b| { + SORT_ORDER + .iter() + .position(|i| i == &a["name"].to_kstr().as_str()) + .unwrap() + .cmp( + &SORT_ORDER + .iter() + .position(|i| i == &b["name"].to_kstr().as_str()) + .unwrap(), + ) + }); let globals = object!({ "metrics": metrics, "images": images, @@ -192,3 +209,40 @@ impl ServerData { self.template = template.render(&globals).unwrap(); } } + +async fn logger(next: &S, ctx: WebContext<'_, C, B>) -> Result> +where + S: for<'r> Service, Response = WebResponse, Error = Error>, +{ + let start = now(); + let request = ctx.req(); + let method = request.method().to_string(); + let url = request.uri().to_string(); + + if &method != "GET" { + // We only allow GET requests + + log(&method, &url, 405, elapsed(start)); + Err(Error::from(StatusCode::METHOD_NOT_ALLOWED)) + } else { + let res = next.call(ctx).await?; + let status = res.status().as_u16(); + + log(&method, &url, status, elapsed(start)); + Ok(res) + } +} + +fn log(method: &str, url: &str, status: u16, time: u32) { + let color = { + if status == 200 { + "\x1b[32m" + } else { + "\x1b[31m" + } + }; + println!( + "\x1b[94;1mHTTP \x1b[0m\x1b[32m{}\x1b[0m {} {}{}\x1b[0m in {}ms", + method, url, color, status, time + ) +} diff --git a/src/structs/image.rs b/src/structs/image.rs index 24f395a..8e9936e 100644 --- a/src/structs/image.rs +++ b/src/structs/image.rs @@ -1,4 +1,4 @@ -use json::{object, JsonValue}; +use serde_json::{json, Value}; use crate::{ config::Config, @@ -38,7 +38,7 @@ pub struct Image { pub digest_info: Option, pub version_info: Option, pub error: Option, - pub time_ms: i64, + pub time_ms: u32, } impl Image { @@ -127,23 +127,23 @@ impl Image { } } - /// Converts image data into a `JsonValue` - pub fn to_json(&self) -> JsonValue { + /// Converts image data into a `Value` + pub fn to_json(&self) -> Value { let has_update = self.has_update(); let update_type = match has_update { Status::UpdateMajor | Status::UpdateMinor | Status::UpdatePatch => "version", _ => "digest", }; - object! { - reference: self.reference.clone(), - parts: object! { - registry: self.registry.clone(), - repository: self.repository.clone(), - tag: self.tag.clone() + json!({ + "reference": self.reference.clone(), + "parts": { + "registry": self.registry.clone(), + "repository": self.repository.clone(), + "tag": self.tag.clone() }, - result: object! { - has_update: has_update.to_option_bool(), - info: match has_update { + "result": { + "has_update": has_update.to_option_bool(), + "info": match has_update { Status::Unknown(_) => None, _ => Some(match update_type { "version" => { @@ -151,35 +151,35 @@ impl Image { Some(data) => (data.current_tag.clone(), data.latest_remote_tag.clone()), _ => unreachable!() }; - object! { + json!({ "type": update_type, - version_update_type: match has_update { + "version_update_type": match has_update { Status::UpdateMajor => "major", Status::UpdateMinor => "minor", Status::UpdatePatch => "patch", _ => unreachable!() }, - new_version: self.tag.replace(&version_tag.to_string(), &latest_remote_tag.as_ref().unwrap().to_string()) - } + "new_version": self.tag.replace(&version_tag.to_string(), &latest_remote_tag.as_ref().unwrap().to_string()) + }) }, "digest" => { let (local_digests, remote_digest) = match &self.digest_info { Some(data) => (data.local_digests.clone(), data.remote_digest.clone()), _ => unreachable!() }; - object! { + json!({ "type": update_type, - local_digests: local_digests, - remote_digest: remote_digest, - } + "local_digests": local_digests, + "remote_digest": remote_digest, + }) }, _ => unreachable!() }), }, - error: self.error.clone() + "error": self.error.clone() }, - time: self.time_ms - } + "time": self.time_ms + }) } /// Checks if the image has an update diff --git a/src/utils/json.rs b/src/utils/json.rs index 71d0d68..ca27c01 100644 --- a/src/utils/json.rs +++ b/src/utils/json.rs @@ -1,11 +1,11 @@ // Functions that return JSON data, used for generating output and API responses -use json::{object, JsonValue}; +use serde_json::{json, Map, Value}; use crate::structs::{image::Image, status::Status}; /// Helper function to get metrics used in JSON output -pub fn get_metrics(updates: &[Image]) -> JsonValue { +pub fn get_metrics(updates: &[Image]) -> Value { let mut up_to_date = 0; let mut major_updates = 0; let mut minor_updates = 0; @@ -35,34 +35,38 @@ pub fn get_metrics(updates: &[Image]) -> JsonValue { } }; }); - object! { - monitored_images: updates.len(), - up_to_date: up_to_date, - updates_available: major_updates + minor_updates + patch_updates + other_updates, - major_updates: major_updates, - minor_updates: minor_updates, - patch_updates: patch_updates, - other_updates: other_updates, - unknown: unknown - } + json!({ + "monitored_images": updates.len(), + "up_to_date": up_to_date, + "updates_available": major_updates + minor_updates + patch_updates + other_updates, + "major_updates": major_updates, + "minor_updates": minor_updates, + "patch_updates": patch_updates, + "other_updates": other_updates, + "unknown": unknown + }) } -/// Takes a slice of `Image` objects and returns a `JsonValue` of update info. The output doesn't contain much detail -pub fn to_simple_json(updates: &[Image]) -> JsonValue { - let mut json_data: JsonValue = object! { - metrics: get_metrics(updates), - images: object! {} - }; +/// Takes a slice of `Image` objects and returns a `Value` with update info. The output doesn't contain much detail +pub fn to_simple_json(updates: &[Image]) -> Value { + let mut images = Map::new(); updates.iter().for_each(|image| { - let _ = json_data["images"].insert(&image.reference, image.has_update().to_option_bool()); + let _ = images.insert( + image.reference.clone(), + image.has_update().to_option_bool().into(), + ); + }); + let json_data: Value = json!({ + "metrics": get_metrics(updates), + "images": images, }); json_data } -/// Takes a slice of `Image` objects and returns a `JsonValue` of update info. All image data is included, useful for debugging. -pub fn to_full_json(updates: &[Image]) -> JsonValue { - object! { - metrics: get_metrics(updates), - images: updates.iter().map(|image| image.to_json()).collect::>(), - } +/// Takes a slice of `Image` objects and returns a `Value` with update info. All image data is included, useful for debugging. +pub fn to_full_json(updates: &[Image]) -> Value { + json!({ + "metrics": get_metrics(updates), + "images": updates.iter().map(|image| image.to_json()).collect::>(), + }) } diff --git a/src/utils/logging.rs b/src/utils/logging.rs index 80463bc..5328227 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -4,7 +4,7 @@ #[macro_export] macro_rules! error { ($($arg:tt)*) => ({ - eprintln!("\x1b[31;1mERROR \x1b[0m {}", format!($($arg)*)); + eprintln!("\x1b[31;1mERROR\x1b[0m {}", format!($($arg)*)); std::process::exit(1); }) } @@ -13,14 +13,14 @@ macro_rules! error { #[macro_export] macro_rules! warn { ($($arg:tt)*) => ({ - eprintln!("\x1b[33;1mWARN \x1b[0m {}", format!($($arg)*)); + eprintln!("\x1b[33;1mWARN \x1b[0m {}", format!($($arg)*)); }) } #[macro_export] macro_rules! info { ($($arg:tt)*) => ({ - println!("\x1b[36;1mINFO \x1b[0m {}", format!($($arg)*)); + println!("\x1b[36;1mINFO \x1b[0m {}", format!($($arg)*)); }) } @@ -28,7 +28,7 @@ macro_rules! info { macro_rules! debug { ($debg:expr, $($arg:tt)*) => ({ if $debg { - println!("\x1b[35;1mDEBUG \x1b[0m {}", format!($($arg)*)); + println!("\x1b[35;1mDEBUG\x1b[0m {}", format!($($arg)*)); } }) } diff --git a/src/utils/misc.rs b/src/utils/misc.rs deleted file mode 100644 index 2b31ad9..0000000 --- a/src/utils/misc.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Miscellaneous utility functions that are too small to go in a separate file - -use chrono::Local; - -/// Gets the current timestamp. Mainly exists so I don't have to type this one line of code ;-) -pub fn timestamp() -> i64 { - Local::now().timestamp_millis() -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index df18720..1e2f96c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,7 @@ pub mod json; pub mod link; pub mod logging; -pub mod misc; pub mod reference; pub mod request; pub mod sort_update_vec; +pub mod time; \ No newline at end of file diff --git a/src/utils/request.rs b/src/utils/request.rs index 180c74b..30c38d8 100644 --- a/src/utils/request.rs +++ b/src/utils/request.rs @@ -1,7 +1,7 @@ use http_auth::parse_challenges; -use json::JsonValue; use reqwest::Response; use rustc_hash::FxHashMap; +use serde_json::Value; use crate::{config::RegistryConfig, error}; @@ -53,8 +53,8 @@ pub async fn get_response_body(response: Response) -> String { } } -pub fn parse_json(body: &str) -> JsonValue { - match json::parse(body) { +pub fn parse_json(body: &str) -> Value { + match serde_json::from_str(body) { Ok(parsed) => parsed, Err(e) => { error!("Failed to parse server response\n{}", e) diff --git a/src/utils/time.rs b/src/utils/time.rs new file mode 100644 index 0000000..ce06d81 --- /dev/null +++ b/src/utils/time.rs @@ -0,0 +1,11 @@ +// When you're too bored to type some things, you get this... + +use std::time::SystemTime; + +pub fn elapsed(start: SystemTime) -> u32 { + start.elapsed().unwrap().as_millis() as u32 +} + +pub fn now() -> SystemTime { + SystemTime::now() +}