From aee7ab9066e81361b34c0eb5426a7024b3e4859a Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 14:52:36 +0530 Subject: [PATCH 1/9] refactor: replace chrono with jiff for time handling Migrate the time-handling logic from chrono to jiff. This updates the public API and internal state to use Zoned and Span types, providing better handling of durations and timezones. The change also includes updates to anyhow and axum dependencies to keep the project current with recent security and performance updates. --- Cargo.lock | 416 +++++++++------------------------------- Cargo.toml | 14 +- examples/leaderboard.rs | 6 +- rust-toolchain.toml | 2 +- src/lib.rs | 65 ++++--- 5 files changed, 136 insertions(+), 367 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ff3cef..b218794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,41 +2,11 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "askama" @@ -80,17 +50,11 @@ dependencies = [ "winnow", ] -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - [[package]] name = "axum" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -107,8 +71,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -122,9 +85,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -133,28 +96,12 @@ dependencies = [ "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - [[package]] name = "basic-toml" version = "0.1.10" @@ -170,54 +117,18 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "cc" -version = "1.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" -dependencies = [ - "shlex", -] - [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "fnv" version = "1.0.7" @@ -278,12 +189,6 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "http" version = "1.3.1" @@ -366,45 +271,51 @@ dependencies = [ ] [[package]] -name = "iana-time-zone" -version = "0.1.63" +name = "itoa" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", + "jiff-static", + "jiff-tzdb-platform", "log", - "wasm-bindgen", - "windows-core", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.59.0", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "jiff-static" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ - "cc", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "io-uring" -version = "0.7.9" +name = "jiff-tzdb" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" [[package]] -name = "itoa" -version = "1.0.15" +name = "jiff-tzdb-platform" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] [[package]] name = "job-watcher" @@ -413,7 +324,7 @@ dependencies = [ "anyhow", "askama", "axum", - "chrono", + "jiff", "rand", "serde", "tokio", @@ -422,16 +333,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "libc" version = "0.2.175" @@ -462,15 +363,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - [[package]] name = "mio" version = "1.0.4" @@ -479,25 +371,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", + "windows-sys 0.59.0", ] [[package]] @@ -524,6 +398,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -586,24 +475,12 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "ryu" version = "1.0.20" @@ -612,18 +489,28 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -664,18 +551,6 @@ dependencies = [ "serde", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - [[package]] name = "smallvec" version = "1.15.1" @@ -689,7 +564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -711,26 +586,23 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", - "io-uring", "libc", "mio", "pin-project-lite", - "slab", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -739,9 +611,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -755,9 +627,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes", @@ -785,9 +657,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -797,9 +669,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -808,9 +680,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -836,130 +708,28 @@ dependencies = [ "wit-bindgen-rt", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-link", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 431daac..93a1f25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,18 +4,18 @@ version = "0.1.0" edition = "2024" [dependencies] -anyhow = "1.0.99" +anyhow = "1.0.101" askama = "0.14.0" -axum = { version = "0.8.4", features = ["json"] } -chrono = { version = "0.4.41", features = ["serde"] } +axum = { version = "0.8.8", features = ["json"] } rand = "0.9.2" serde = { version = "1.0.219", features = ["derive", "rc"] } -tokio = { version = "1.47.1", features = ["rt-multi-thread"]} -tower = "0.5.2" -tower-http = { version = "0.6.6", features = [ +jiff = { version = "0.2.19", features = ["serde"]} +tokio = { version = "1.49.0", features = ["rt-multi-thread"]} +tower = "0.5.3" +tower-http = { version = "0.6.8", features = [ "cors", "limit", "timeout", "trace", ] } -tracing = "0.1.41" +tracing = "0.1.44" diff --git a/examples/leaderboard.rs b/examples/leaderboard.rs index 864d7b0..6ca424b 100644 --- a/examples/leaderboard.rs +++ b/examples/leaderboard.rs @@ -1,5 +1,5 @@ use anyhow::{Result, bail}; -use chrono::{DateTime, Utc}; +use jiff::Zoned; use job_watcher::{ AppBuilder, Heartbeat, TaskLabel, WatchedTask, WatchedTaskOutput, WatcherAppContext, config::{Delay, TaskConfig, WatcherConfig}, @@ -39,8 +39,8 @@ impl WatcherAppContext for DummyApp { Some("test-env".to_string()) } - fn live_since(&self) -> DateTime { - Utc::now() + fn live_since(&self) -> Zoned { + Zoned::now() } fn watcher_config(&self) -> WatcherConfig { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c1bc0a6..ff100ed 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.85.0" +channel = "1.90.0" diff --git a/src/lib.rs b/src/lib.rs index b36cca0..01f80b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,8 +30,8 @@ use axum::{ http::{self, HeaderValue}, response::IntoResponse, }; -use chrono::{DateTime, Duration, Utc}; use config::{Delay, WatcherConfig}; +use jiff::{Span, Zoned}; use rand::Rng; use std::fmt::Write; use std::{borrow::Cow, collections::HashMap, fmt::Display, pin::Pin, sync::Arc, time::Instant}; @@ -41,7 +41,7 @@ pub trait WatcherAppContext { fn title(&self) -> String; fn environment(&self) -> Option; fn build_version(&self) -> Option; - fn live_since(&self) -> DateTime; + fn live_since(&self) -> Zoned; fn watcher_config(&self) -> WatcherConfig; fn triggers_alert(&self, label: &TaskLabel, selected_label: Option<&TaskLabel>) -> bool; fn show_output(&self, label: &TaskLabel) -> bool; @@ -52,7 +52,6 @@ pub trait WatcherAppContext { )] #[serde(transparent)] pub struct TaskLabel(String); - impl TaskLabel { pub fn new(s: impl Into) -> Self { Self(s.into()) @@ -98,7 +97,7 @@ impl TaskResultValue { #[serde(rename_all = "kebab-case")] pub struct TaskResult { pub value: Arc, - pub updated: DateTime, + pub updated: Zoned, } #[derive(Clone, serde::Serialize, Debug)] @@ -106,18 +105,18 @@ pub struct TaskResult { pub struct TaskError { #[serde(skip)] pub value: Arc, - pub updated: DateTime, + pub updated: Zoned, } impl TaskResult { fn since(&self) -> Since { - Since(self.updated) + Since(self.updated.clone()) } } impl TaskError { fn since(&self) -> Since { - Since(self.updated) + Since(self.updated.clone()) } } @@ -140,10 +139,10 @@ impl TaskCounts { pub struct TaskStatus { pub last_result: TaskResult, pub last_retry_error: Option, - pub current_run_started: Option>, + pub current_run_started: Option, /// Is the last_result out of date ? #[serde(skip)] - pub out_of_date: Option, + pub out_of_date: Option, /// Should we expire the status of last result ? #[serde(skip)] pub expire_last_result: Option<(std::time::Duration, Instant)>, @@ -237,8 +236,8 @@ struct StatusTemplate<'a> { statuses: Vec, env: Cow<'a, str>, build_version: Cow<'a, str>, - live_since: DateTime, - now: DateTime, + live_since: Zoned, + now: Zoned, alert: bool, title: String, } @@ -256,7 +255,7 @@ impl TaskStatuses { statuses, env: app.environment().unwrap_or("Unknown".to_owned()).into(), build_version: app.build_version().unwrap_or("Unknown".to_owned()).into(), - now: Utc::now(), + now: Zoned::now(), alert, live_since: app.live_since(), title: app.title(), @@ -457,11 +456,11 @@ impl TaskStatus { } fn is_out_of_date(&self) -> OutOfDateType { - match self.current_run_started { + match &self.current_run_started { Some(started) => match self.out_of_date { Some(out_of_date) => { - let now = Utc::now(); - if started + Duration::seconds(300) <= now { + let now = Zoned::now(); + if started.clone() + Span::new().seconds(300) <= now { OutOfDateType::Very } else if started + out_of_date <= now { OutOfDateType::Slightly @@ -509,12 +508,12 @@ impl TaskStatus { } } -struct Since(DateTime); +struct Since(Zoned); impl Display for Since { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let duration = Utc::now().signed_duration_since(self.0); - let secs = duration.num_seconds(); + let duration = Zoned::now().duration_since(&self.0); + let secs = duration.as_secs(); match secs.cmp(&0) { std::cmp::Ordering::Less => write!(f, "{}", self.0), @@ -717,11 +716,11 @@ impl AppBuilder { let config = task_config_for(&self.app.watcher_config(), &label); let out_of_date = config .out_of_date - .map(|item| chrono::Duration::seconds(item.into())); + .map(|seconds| Span::new().seconds(seconds)); let task_status = Arc::new(RwLock::new(TaskStatus { last_result: TaskResult { value: TaskResultValue::NotYetRun.into(), - updated: Utc::now(), + updated: Zoned::now(), }, last_retry_error: None, current_run_started: None, @@ -750,7 +749,7 @@ impl AppBuilder { *guard = TaskStatus { last_result: old.last_result.clone(), last_retry_error: old.last_retry_error.clone(), - current_run_started: Some(Utc::now()), + current_run_started: Some(Zoned::now()), out_of_date, counts: old.counts, expire_last_result: old.expire_last_result, @@ -801,9 +800,9 @@ impl AppBuilder { } } let last_run_seconds = { - if let Some(old_run_started) = old.current_run_started { - let duration = Utc::now() - old_run_started; - Some(duration.num_seconds()) + if let Some(old_run_started) = old.current_run_started.clone() { + let duration = Zoned::now() - old_run_started; + Some(duration.get_seconds()) } else { None } @@ -817,7 +816,7 @@ impl AppBuilder { } else { TaskResultValue::Ok(message).into() }, - updated: Utc::now(), + updated: Zoned::now(), }, last_retry_error: None, current_run_started: None, @@ -875,9 +874,9 @@ impl AppBuilder { let mut guard = task_status.write().await; let old = &*guard; let last_run_seconds = { - if let Some(old_run_started) = old.current_run_started { - let duration = Utc::now() - old_run_started; - Some(duration.num_seconds()) + if let Some(old_run_started) = old.current_run_started.clone() { + let duration = Zoned::now() - old_run_started; + Some(duration.get_seconds()) } else { None } @@ -905,7 +904,7 @@ impl AppBuilder { *guard = TaskStatus { last_result: TaskResult { value: TaskResultValue::Err(new_error_message).into(), - updated: Utc::now(), + updated: Zoned::now(), }, last_retry_error: None, current_run_started: None, @@ -922,9 +921,9 @@ impl AppBuilder { let mut guard = task_status.write().await; let old = &*guard; let last_run_seconds = { - if let Some(old_run_started) = old.current_run_started { - let duration = Utc::now() - old_run_started; - Some(duration.num_seconds()) + if let Some(old_run_started) = old.current_run_started.clone() { + let duration = Zoned::now() - old_run_started; + Some(duration.get_seconds()) } else { None } @@ -933,7 +932,7 @@ impl AppBuilder { last_result: old.last_result.clone(), last_retry_error: Some(TaskError { value: format!("{err:?}").into(), - updated: Utc::now(), + updated: Zoned::now(), }), current_run_started: None, out_of_date, From 603afb2642fce84d2583d2f76745b6ba8674705c Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 16:00:22 +0530 Subject: [PATCH 2/9] refactor: use Infallible for long-running tasks The task management system now uses `std::convert::Infallible` to explicitly signal that background tasks and the REST API server are intended to run indefinitely. This removes the need for the internal `StoppedTask` enum and simplifies error handling when tasks unexpectedly terminate. --- Cargo.lock | 16 +++++++-------- Cargo.toml | 2 +- src/lib.rs | 52 ++++++++++++++++--------------------------------- src/rest_api.rs | 9 +++++---- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b218794..684e0eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,18 +424,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.105" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "wasi" diff --git a/Cargo.toml b/Cargo.toml index 93a1f25..bbb09ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ anyhow = "1.0.101" askama = "0.14.0" axum = { version = "0.8.8", features = ["json"] } rand = "0.9.2" -serde = { version = "1.0.219", features = ["derive", "rc"] } +serde = { version = "1.0.228", features = ["derive", "rc"] } jiff = { version = "0.2.19", features = ["serde"]} tokio = { version = "1.49.0", features = ["rt-multi-thread"]} tower = "0.5.3" diff --git a/src/lib.rs b/src/lib.rs index 01f80b5..5c757c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,8 +33,8 @@ use axum::{ use config::{Delay, WatcherConfig}; use jiff::{Span, Zoned}; use rand::Rng; -use std::fmt::Write; use std::{borrow::Cow, collections::HashMap, fmt::Display, pin::Pin, sync::Arc, time::Instant}; +use std::{convert::Infallible, fmt::Write}; use tokio::{net::TcpListener, sync::RwLock, task::JoinSet}; pub trait WatcherAppContext { @@ -69,7 +69,8 @@ impl Display for TaskLabel { } struct ToSpawn { - future: Pin> + Send>>, + /// This future is expected to be an infinite loop and never return Ok. + future: Pin> + Send>>, label: TaskLabel, } @@ -152,16 +153,10 @@ pub struct TaskStatus { pub(crate) type StatusMap = HashMap>>; -enum StoppedTask { - Background, - Periodic(TaskLabel), - RestApi, -} - #[derive(Default)] pub(crate) struct Watcher { to_spawn: Vec, - set: JoinSet>, + set: JoinSet>, statuses: StatusMap, } @@ -555,23 +550,18 @@ impl Watcher { app: Arc, listener: TcpListener, ) -> Result<()> { - self.set.spawn(async { - rest_api::start_rest_api( - app, - TaskStatuses { - statuses: Arc::new(self.statuses), - }, - listener, - ) - .await?; - Ok(StoppedTask::RestApi) - }); + self.set.spawn(rest_api::start_rest_api( + app, + TaskStatuses { + statuses: Arc::new(self.statuses), + }, + listener, + )); for ToSpawn { future, label } in self.to_spawn { self.set.spawn(async move { future .await - .with_context(|| format!("Failure while running: {label}"))?; - Ok(StoppedTask::Periodic(label)) + .with_context(|| format!("Task failed: {}", label)) }); } if let Some(res) = self.set.join_next().await { @@ -580,14 +570,9 @@ impl Watcher { self.set.abort_all(); return Err(e); } - Ok(task) => { - self.set.abort_all(); - let task_label = match task { - StoppedTask::Background => "background task".into(), - StoppedTask::Periodic(task_label) => format!("task tracking {task_label}"), - StoppedTask::RestApi => "REST API".into(), - }; - anyhow::bail!("Unexpected finish of {task_label}") + Ok(_task) => { + // This branch is impossible, as Infallible can't be created. + anyhow::bail!("Impossible: Infallible witnessed!") } } } @@ -691,12 +676,9 @@ impl AppBuilder { /// Watch a background job that runs continuously, launched immediately pub fn watch_background(&mut self, task: Fut) where - Fut: std::future::Future> + Send + 'static, + Fut: std::future::Future> + Send + 'static, { - self.watcher.set.spawn(async { - task.await?; - Ok(StoppedTask::Background) - }); + self.watcher.set.spawn(task); } pub fn watch_periodic(&mut self, label: TaskLabel, mut task: T) -> Result<()> diff --git a/src/rest_api.rs b/src/rest_api.rs index 0b8deeb..95a96b5 100644 --- a/src/rest_api.rs +++ b/src/rest_api.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{convert::Infallible, sync::Arc}; use anyhow::Result; use axum::{ @@ -31,7 +31,7 @@ pub(crate) async fn start_rest_api, statuses: TaskStatuses, listener: TcpListener, -) -> Result<()> { +) -> Result { let service_builder = ServiceBuilder::new() .layer( TraceLayer::new_for_http() @@ -60,8 +60,9 @@ pub(crate) async fn start_rest_api &'static str { From 10f5f56fb3a400dbf5830c711d6a1bf6cb75ab55 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 16:22:31 +0530 Subject: [PATCH 3/9] Github action --- .github/workflows/rust.yaml | 48 +++++++++++++++++++++++++++++++++++++ examples/leaderboard.rs | 6 +---- justfile | 12 ++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/rust.yaml diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml new file mode 100644 index 0000000..a337eed --- /dev/null +++ b/.github/workflows/rust.yaml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + RUST_BACKTRACE: 1 + RUSTUP_MAX_RETRIES: 10 + RUSTFLAGS: "-D warnings" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + tests: + name: Rust + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - uses: taiki-e/install-action@v2 + with: + tool: just@1.43.1 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.90.0 + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + prefix-key: "v1" + cache-workspace-crates: true + cache-on-failure: true + workspaces: | + . + - name: Compile + run: just cargo-compile + - name: Clippy + run: just cargo-clippy-check + - name: Rustfmt + run: just cargo-fmt-check diff --git a/examples/leaderboard.rs b/examples/leaderboard.rs index 6ca424b..af87119 100644 --- a/examples/leaderboard.rs +++ b/examples/leaderboard.rs @@ -68,11 +68,7 @@ impl WatcherAppContext for DummyApp { fn triggers_alert(&self, label: &TaskLabel, _selected_label: Option<&TaskLabel>) -> bool { // In a real app, you might have different logic for different tasks. - if label.ident() == "leaderboard" { - false - } else { - true - } + label.ident() != "leaderboard" } fn show_output(&self, _label: &TaskLabel) -> bool { diff --git a/justfile b/justfile index f65442a..6bb3513 100644 --- a/justfile +++ b/justfile @@ -2,6 +2,18 @@ default: just --list --unsorted +# cargo compile +cargo-compile: + cargo test --workspace --no-run --locked + +# Clippy check +cargo-clippy-check: + cargo clippy --no-deps --workspace --locked --tests --benches --examples -- -Dwarnings + +# Rustfmt check +cargo-fmt-check: + cargo fmt --all --check + # Run example example: cargo run --example leaderboard From 8bdbc413319b0e1bd8b6fa2ad36ee97cfc7964cd Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 16:32:50 +0530 Subject: [PATCH 4/9] Better UI using tailwind. --- templates/status.html | 300 +++++++++++++++++++++++++++++++----------- 1 file changed, 220 insertions(+), 80 deletions(-) diff --git a/templates/status.html b/templates/status.html index 95e93b1..48d4008 100644 --- a/templates/status.html +++ b/templates/status.html @@ -1,100 +1,240 @@ - + - {{ title }} - - - - - + + + + - -
-
-

{{ title }}

-
-
-
-
Environment {{env}}
-
Version {{build_version}}
-
Live since {{live_since}}
-
Current time {{now}}
-
-
-
- - - - - - + +
+ + +
+
+

{{ title }}

+
{{ now }}
+
+
+ +
+ + +
+
+
Environment
+
{{env}}
+
+
+
Version
+
{{build_version}}
+
+
+
Live since
+
{{live_since}}
+
+
+ + +
+
+

Task Overview

+
+
    {% for status in statuses %} -
- - - +
  • + +
    +
    +
    + + + + + +
    +

    {{ status.label }}

    +
    +
    +
    {{ status.short.as_str() }}
    + +
    +
    +
    +
  • {% endfor %} - -
    TaskStatus
    - {{ status.label }} - - {{ status.short.as_str() }} -
    -
    -
    - {% for status in statuses %} -
    -

    {{ status.label }}

    -

    Status: {{ status.short.as_str() }} {{ status.status.last_result.since() }} | Last Run time (Seconds): {{ status.status.total_run_time() }}

    -

    Successes: {{ status.status.counts.successes }} Retries: {{ status.status.counts.retries }} Errors: {{ status.status.counts.errors }}

    - {% if let Some(started) = status.status.current_run_started.as_ref() %} -

    Currently running, started at {{ started }}

    - {% endif %} + +
    + + +
    + {% for status in statuses %} +
    + +
    +
    +

    + {{ status.label }} + + {{ status.short.as_str() }} + +

    +

    + Last updated: {{ status.status.last_result.since() }} | Run time: {{ status.status.total_run_time() }}s +

    +
    +
    + +
    +
    + +
    + + +
    +
    +
    Successes
    +
    {{ status.status.counts.successes }}
    +
    +
    +
    Retries
    +
    {{ status.status.counts.retries }}
    +
    +
    +
    Errors
    +
    {{ status.status.counts.errors }}
    +
    +
    + + + {% if let Some(started) = status.status.current_run_started.as_ref() %} +
    +
    +
    + + + + +
    +
    +

    Task Currently Running

    +
    +

    Started at {{ started }}

    +
    +
    +
    +
    + {% endif %} + + +
    +
    + Output +
    +
    {{ status.status.last_result.value.as_ref().as_str() }}
    +
    + + + {% if let Some(retrying) = status.status.last_retry_error.as_ref() %} +
    +
    +
    +
    + +
    +
    +

    Retry in progress

    +
    +

    Last error occurred {{ retrying.since() }}

    +
    +
    {{ retrying.value }}
    +
    +
    +
    +
    +
    +
    + {% endif %} -
    {{ status.status.last_result.value.as_ref().as_str() }}
    +
    +
    + {% endfor %} +
    + +
    +

    © Job Watcher - {{ title }}

    +
    - {% if let Some(retrying) = status.status.last_retry_error.as_ref() %} -

    Currently retrying, last error message {{ retrying.since() }}

    -
    {{ retrying.value }}
    - {% endif %} -
    - {% endfor %} +
    From e893c84eaa6cd0871b57debb464921d30b6902ad Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 16:39:42 +0530 Subject: [PATCH 5/9] Update --- src/lib.rs | 3 ++- templates/status.html | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5c757c4..f18f7e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -245,12 +245,13 @@ impl TaskStatuses { ) -> StatusTemplate<'a> { let statuses = self.statuses(app, label).await; let alert = statuses.iter().any(|x| x.short.alert()); + let now = Zoned::now(); StatusTemplate { statuses, env: app.environment().unwrap_or("Unknown".to_owned()).into(), build_version: app.build_version().unwrap_or("Unknown".to_owned()).into(), - now: Zoned::now(), + now, alert, live_since: app.live_since(), title: app.title(), diff --git a/templates/status.html b/templates/status.html index 48d4008..c940886 100644 --- a/templates/status.html +++ b/templates/status.html @@ -78,7 +78,7 @@

    {{ title }}

    -
    {{ now }}
    +
    {{ now.strftime("%Y-%m-%d %H:%M:%S") }}
    @@ -96,7 +96,7 @@

    Live since
    -
    {{live_since}}
    +
    {{live_since.strftime("%Y-%m-%d %H:%M:%S")}}
    From a0274f87f05e37b65b4a217340a91e321f5c5b21 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 16:43:11 +0530 Subject: [PATCH 6/9] Update --- examples/leaderboard.rs | 2 +- templates/status.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/leaderboard.rs b/examples/leaderboard.rs index af87119..3bf40db 100644 --- a/examples/leaderboard.rs +++ b/examples/leaderboard.rs @@ -76,7 +76,7 @@ impl WatcherAppContext for DummyApp { } fn build_version(&self) -> Option { - Some("dfa2test".to_owned()) + Some("dfa2testdkafjakfjakjkafjkafjakfjkajkajkajk".to_owned()) } fn title(&self) -> String { diff --git a/templates/status.html b/templates/status.html index c940886..cb7a4db 100644 --- a/templates/status.html +++ b/templates/status.html @@ -147,7 +147,7 @@

    - Last updated: {{ status.status.last_result.since() }} | Run time: {{ status.status.total_run_time() }}s + Last updated: {{ status.status.last_result.updated.strftime("%Y-%m-%d %H:%M:%S") }} | Run time: {{ status.status.total_run_time() }}s

    @@ -186,7 +186,7 @@

    Task Currently Running

    -

    Started at {{ started }}

    +

    Started at {{ started.strftime("%Y-%m-%d %H:%M:%S") }}

    @@ -214,7 +214,7 @@

    Task Currently Running

    Retry in progress

    -

    Last error occurred {{ retrying.since() }}

    +

    Last error occurred {{ retrying.updated.strftime("%Y-%m-%d %H:%M:%S") }}

    {{ retrying.value }}
    From 9f198faff4690f5e86227dd503cfe73bc65809b2 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 16:44:40 +0530 Subject: [PATCH 7/9] Update --- templates/status.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/status.html b/templates/status.html index cb7a4db..2a1bc3b 100644 --- a/templates/status.html +++ b/templates/status.html @@ -92,7 +92,7 @@

    Version
    -
    {{build_version}}
    +
    {{build_version}}

    Live since
    From c76d993798b8f9b9e60507aac360f986e28c71e9 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 7 Feb 2026 16:46:15 +0530 Subject: [PATCH 8/9] Ability to copy via clipboard --- templates/status.html | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/templates/status.html b/templates/status.html index 2a1bc3b..51b2e8c 100644 --- a/templates/status.html +++ b/templates/status.html @@ -195,10 +195,10 @@

    Task Currently Running

    -
    - Output +
    +
    -
    {{ status.status.last_result.value.as_ref().as_str() }}
    +
    {{ status.status.last_result.value.as_ref().as_str() }}
    @@ -236,5 +236,28 @@

    Retry in progress

    + From 67d809a14f11b53817f1fad1c02b5fe864ef4a7e Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Mon, 9 Feb 2026 08:22:02 +0530 Subject: [PATCH 9/9] Do clippy fix --- examples/leaderboard.rs | 6 +-- src/lib.rs | 93 ++++++++++++++++++++--------------------- templates/status.html | 4 +- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/examples/leaderboard.rs b/examples/leaderboard.rs index 3bf40db..1019c92 100644 --- a/examples/leaderboard.rs +++ b/examples/leaderboard.rs @@ -16,7 +16,7 @@ struct LeaderBoard; struct TaskTwo; #[derive(Clone)] -struct DummyApp; +struct DummyApp(Zoned); impl DummyApp { async fn start() -> Result<()> { @@ -24,7 +24,7 @@ impl DummyApp { println!("Starting leaderboard watcher example."); println!("The watcher will run, but the status page is not served in this example."); - let app = Arc::new(DummyApp); + let app = Arc::new(DummyApp(Zoned::now())); let mut builder = AppBuilder::new(app.clone()); builder.watch_periodic(TaskLabel::new("leaderboard"), LeaderBoard)?; @@ -40,7 +40,7 @@ impl WatcherAppContext for DummyApp { } fn live_since(&self) -> Zoned { - Zoned::now() + self.0.clone() } fn watcher_config(&self) -> WatcherConfig { diff --git a/src/lib.rs b/src/lib.rs index f18f7e7..2a22c48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,18 +109,6 @@ pub struct TaskError { pub updated: Zoned, } -impl TaskResult { - fn since(&self) -> Since { - Since(self.updated.clone()) - } -} - -impl TaskError { - fn since(&self) -> Since { - Since(self.updated.clone()) - } -} - #[derive(Clone, Copy, Default, serde::Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct TaskCounts { @@ -237,6 +225,51 @@ struct StatusTemplate<'a> { title: String, } +impl<'a> StatusTemplate<'a> { + fn live_since_human(&self) -> String { + let duration = self.now.duration_since(&self.live_since); + let secs = duration.as_secs(); + + if secs < 0 { + return "In the future".to_string(); + } + if secs == 0 { + return "just now".to_string(); + } + + let minutes = secs / 60; + let secs_rem = secs % 60; + let hours = minutes / 60; + let minutes_rem = minutes % 60; + let days = hours / 24; + let hours_rem = hours % 24; + + let mut result = String::new(); + let mut need_space = false; + + for (number, letter) in [ + (days, "d"), + (hours_rem, "h"), + (minutes_rem, "m"), + (secs_rem, "s"), + ] { + if number > 0 { + if need_space { + result.push(' '); + } + result.push_str(&format!("{}{}", number, letter)); + need_space = true; + } + } + + if result.is_empty() { + "0s".to_string() + } else { + result + } + } +} + impl TaskStatuses { async fn to_template<'a, C: WatcherAppContext>( &'a self, @@ -504,42 +537,6 @@ impl TaskStatus { } } -struct Since(Zoned); - -impl Display for Since { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let duration = Zoned::now().duration_since(&self.0); - let secs = duration.as_secs(); - - match secs.cmp(&0) { - std::cmp::Ordering::Less => write!(f, "{}", self.0), - std::cmp::Ordering::Equal => write!(f, "just now ({})", self.0), - std::cmp::Ordering::Greater => { - let minutes = secs / 60; - let secs = secs % 60; - let hours = minutes / 60; - let minutes = minutes % 60; - let days = hours / 24; - let hours = hours % 24; - - let mut need_space = false; - for (number, letter) in [(days, 'd'), (hours, 'h'), (minutes, 'm'), (secs, 's')] { - if number > 0 { - if need_space { - write!(f, " {number}{letter}")?; - } else { - need_space = true; - write!(f, "{number}{letter}")?; - } - } - } - - write!(f, " ({})", self.0) - } - } - } -} - pub struct AppBuilder { pub app: Arc, pub(crate) watcher: Watcher, diff --git a/templates/status.html b/templates/status.html index 51b2e8c..84b61f5 100644 --- a/templates/status.html +++ b/templates/status.html @@ -96,7 +96,7 @@

    Live since
    -
    {{live_since.strftime("%Y-%m-%d %H:%M:%S")}}
    +
    {{live_since_human()}}

    @@ -231,7 +231,7 @@

    Retry in progress