From 1f7cd4ded8db57fb5ec3f7d42782fe45a70af164 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:19:02 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 1041 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 + README.md | 3 + src/client.rs | 333 ++++++++++++++++ src/lib.rs | 288 ++++++++++++++ 6 files changed, 1684 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/client.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1de5659 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fccda9d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1041 @@ +# This file is automatically @generated by Cargo. +# 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 = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[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 = "bitflags" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" + +[[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.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[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 = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.3+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 = "hero-job" +version = "0.1.0" +dependencies = [ + "chrono", + "log", + "redis", + "serde", + "serde_json", + "thiserror", + "uuid", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[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", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redis" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec" +dependencies = [ + "async-trait", + "bytes", + "combine", + "futures-util", + "itoa", + "percent-encoding", + "pin-project-lite", + "ryu", + "sha1_smol", + "socket2 0.5.10", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2 0.6.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.3+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +dependencies = [ + "wit-bindgen", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..38e67d2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "hero-job" +version = "0.1.0" +edition = "2021" +description = "Job types and utilities for the Hero ecosystem" + +[dependencies] +chrono = { version = "0.4", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "1.0", features = ["v4"] } +thiserror = "1.0" +redis = { version = "0.25", features = ["aio", "tokio-comp"] } +log = "0.4" + +[lib] +name = "hero_job" +path = "src/lib.rs" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e38fc93 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Job + +Job model and client for supervisor \ No newline at end of file diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..6e0fe36 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,333 @@ +//! Job client implementation for managing jobs in Redis + +use chrono::Utc; +use redis::AsyncCommands; +use crate::{Job, JobStatus, JobError}; + +/// Client for managing jobs in Redis +#[derive(Debug, Clone)] +pub struct Client { + redis_client: redis::Client, + namespace: String, +} + +pub struct ClientBuilder { + /// Redis URL for connection + redis_url: String, + /// Namespace for queue keys + namespace: String, +} + +impl ClientBuilder { + /// Create a new client builder + pub fn new() -> Self { + Self { + redis_url: "redis://localhost:6379".to_string(), + namespace: "".to_string(), + } + } + + /// Set the Redis URL + pub fn redis_url>(mut self, url: S) -> Self { + self.redis_url = url.into(); + self + } + + /// Set the namespace for queue keys + pub fn namespace>(mut self, namespace: S) -> Self { + self.namespace = namespace.into(); + self + } + + /// Build the client + pub async fn build(self) -> Result { + // Create Redis client + let redis_client = redis::Client::open(self.redis_url.as_str()) + .map_err(|e| JobError::Redis(e))?; + + Ok(Client { + redis_client, + namespace: self.namespace, + }) + } +} + +impl Default for Client { + fn default() -> Self { + // Note: Default implementation creates an empty client + // Use Client::builder() for proper initialization + Self { + redis_client: redis::Client::open("redis://localhost:6379").unwrap(), + namespace: "".to_string(), + } + } +} + +impl Client { + /// Create a new client builder + pub fn builder() -> ClientBuilder { + ClientBuilder::new() + } + + /// List all job IDs from Redis + pub async fn list_jobs(&self) -> Result, JobError> { + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + let keys: Vec = conn.keys(format!("{}:*", &self.jobs_key())).await + .map_err(|e| JobError::Redis(e))?; + let job_ids: Vec = keys + .into_iter() + .filter_map(|key| { + if key.starts_with(&format!("{}:", self.jobs_key())) { + key.strip_prefix(&format!("{}:", self.jobs_key())) + .map(|s| s.to_string()) + } else { + None + } + }) + .collect(); + + Ok(job_ids) + } + + fn jobs_key(&self) -> String { + if self.namespace.is_empty() { + format!("job") + } else { + format!("{}:job", self.namespace) + } + } + + pub fn job_key(&self, job_id: &str) -> String { + if self.namespace.is_empty() { + format!("job:{}", job_id) + } else { + format!("{}:job:{}", self.namespace, job_id) + } + } + + pub fn job_reply_key(&self, job_id: &str) -> String { + if self.namespace.is_empty() { + format!("reply:{}", job_id) + } else { + format!("{}:reply:{}", self.namespace, job_id) + } + } + + pub fn runner_key(&self, runner_name: &str) -> String { + if self.namespace.is_empty() { + format!("runner:{}", runner_name) + } else { + format!("{}:runner:{}", self.namespace, runner_name) + } + } + + /// Set job error in Redis + pub async fn set_error(&self, + job_id: &str, + error: &str, + ) -> Result<(), JobError> { + let job_key = self.job_key(job_id); + let now = Utc::now(); + + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + conn.hset_multiple(&job_key, &[ + ("error", error), + ("status", JobStatus::Error.as_str()), + ("updated_at", &now.to_rfc3339()), + ]).await + .map_err(|e| JobError::Redis(e))?; + + Ok(()) + } + + /// Set job status in Redis + pub async fn set_job_status(&self, + job_id: &str, + status: JobStatus, + ) -> Result<(), JobError> { + let job_key = self.job_key(job_id); + let now = Utc::now(); + + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + conn.hset_multiple(&job_key, &[ + ("status", status.as_str()), + ("updated_at", &now.to_rfc3339()), + ]).await + .map_err(|e| JobError::Redis(e))?; + Ok(()) + } + + /// Get job status from Redis + pub async fn get_status( + &self, + job_id: &str, + ) -> Result { + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + let status_str: Option = conn.hget(&self.job_key(job_id), "status").await + .map_err(|e| JobError::Redis(e))?; + + match status_str { + Some(s) => JobStatus::from_str(&s).ok_or_else(|| JobError::InvalidStatus(s)), + None => Err(JobError::NotFound(job_id.to_string())), + } + } + + /// Delete job from Redis + pub async fn delete_from_redis( + &self, + job_id: &str, + ) -> Result<(), JobError> { + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + let job_key = self.job_key(job_id); + let _: () = conn.del(&job_key).await + .map_err(|e| JobError::Redis(e))?; + Ok(()) + } + + /// Store this job in Redis + pub async fn store_job_in_redis(&self, job: &Job) -> Result<(), JobError> { + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + let job_key = self.job_key(&job.id); + + // Serialize the job data + let job_data = serde_json::to_string(job) + .map_err(|e| JobError::Serialization(e.to_string()))?; + + // Store job data in Redis hash + let _: () = conn.hset_multiple(&job_key, &[ + ("data", job_data), + ("status", JobStatus::Dispatched.as_str().to_string()), + ("created_at", job.created_at.to_rfc3339()), + ("updated_at", job.updated_at.to_rfc3339()), + ]).await + .map_err(|e| JobError::Redis(e))?; + + // Set TTL for the job (24 hours) + let _: () = conn.expire(&job_key, 86400).await + .map_err(|e| JobError::Redis(e))?; + + Ok(()) + } + + /// Load a job from Redis by ID + pub async fn load_job_from_redis( + &self, + job_id: &str, + ) -> Result { + let job_key = self.job_key(job_id); + + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + // Get job data from Redis + let job_data: Option = conn.hget(&job_key, "data").await + .map_err(|e| JobError::Redis(e))?; + + match job_data { + Some(data) => { + let job: Job = serde_json::from_str(&data) + .map_err(|e| JobError::Serialization(e.to_string()))?; + Ok(job) + } + None => Err(JobError::NotFound(job_id.to_string())), + } + } + + /// Delete a job by ID + pub async fn delete_job(&mut self, job_id: &str) -> Result<(), JobError> { + let mut conn = self.redis_client.get_multiplexed_async_connection().await + .map_err(|e| JobError::Redis(e))?; + + let job_key = self.job_key(job_id); + let deleted_count: i32 = conn.del(&job_key).await + .map_err(|e| JobError::Redis(e))?; + + if deleted_count == 0 { + return Err(JobError::NotFound(job_id.to_string())); + } + + Ok(()) + } + + /// Set job result in Redis + pub async fn set_result( + &self, + job_id: &str, + result: &str, + ) -> Result<(), JobError> { + let job_key = self.job_key(&job_id); + let now = Utc::now(); + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + let _: () = conn.hset_multiple(&job_key, &[ + ("result", result), + ("status", JobStatus::Finished.as_str()), + ("updated_at", &now.to_rfc3339()), + ]).await + .map_err(|e| JobError::Redis(e))?; + + Ok(()) + } + + /// Get job result from Redis + pub async fn get_result( + &self, + job_id: &str, + ) -> Result, JobError> { + let job_key = self.job_key(job_id); + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + let result: Option = conn.hget(&job_key, "result").await + .map_err(|e| JobError::Redis(e))?; + Ok(result) + } + + /// Get a job ID from the work queue (blocking pop) + pub async fn get_job_id(&self, queue_key: &str) -> Result, JobError> { + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + // Use BRPOP with a short timeout to avoid blocking indefinitely + let result: Option<(String, String)> = conn.brpop(queue_key, 1.0).await + .map_err(|e| JobError::Redis(e))?; + + Ok(result.map(|(_, job_id)| job_id)) + } + + /// Get a job by ID (alias for load_job_from_redis) + pub async fn get_job(&self, job_id: &str) -> Result { + self.load_job_from_redis(job_id).await + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4261364 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,288 @@ +use chrono::{DateTime, Utc}; +use redis::AsyncCommands; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use thiserror::Error; +use uuid::Uuid; +use log::{debug, error}; + +pub mod client; +pub use client::Client; + +/// Job status enumeration +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum JobStatus { + Dispatched, + WaitingForPrerequisites, + Started, + Error, + Stopping, + Finished, +} + +impl JobStatus { + pub fn as_str(&self) -> &'static str { + match self { + JobStatus::Dispatched => "dispatched", + JobStatus::WaitingForPrerequisites => "waiting_for_prerequisites", + JobStatus::Started => "started", + JobStatus::Error => "error", + JobStatus::Stopping => "stopping", + JobStatus::Finished => "finished", + } + } + + pub fn from_str(s: &str) -> Option { + match s { + "dispatched" => Some(JobStatus::Dispatched), + "waiting_for_prerequisites" => Some(JobStatus::WaitingForPrerequisites), + "started" => Some(JobStatus::Started), + "error" => Some(JobStatus::Error), + "stopping" => Some(JobStatus::Stopping), + "finished" => Some(JobStatus::Finished), + _ => None, + } + } +} + +/// Representation of a script execution request. +/// +/// This structure contains all the information needed to execute a script +/// on a actor service, including the script content, dependencies, and metadata. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Job { + pub id: String, + pub caller_id: String, + pub context_id: String, + pub payload: String, + pub runner: String, // name of the runner to execute this job + pub executor: String, // name of the executor the runner will use to execute this job + pub timeout: u64, // timeout in seconds + pub env_vars: HashMap, // environment variables for script execution + pub created_at: chrono::DateTime, + pub updated_at: chrono::DateTime, +} + +/// Error types for job operations +#[derive(Error, Debug)] +pub enum JobError { + #[error("Redis error: {0}")] + Redis(#[from] redis::RedisError), + #[error("Serialization error: {0}")] + Serialization(String), + #[error("Job not found: {0}")] + NotFound(String), + #[error("Invalid job status: {0}")] + InvalidStatus(String), + #[error("Timeout error: {0}")] + Timeout(String), + #[error("Invalid job data: {0}")] + InvalidData(String), +} + +impl Job { + /// Create a new job with the given parameters + pub fn new( + caller_id: String, + context_id: String, + payload: String, + runner: String, + executor: String, + ) -> Self { + let now = Utc::now(); + Self { + id: Uuid::new_v4().to_string(), + caller_id, + context_id, + payload, + runner, + executor, + timeout: 300, // 5 minutes default + env_vars: HashMap::new(), + created_at: now, + updated_at: now, + } + } + + /// Update job status in Redis using default client + pub async fn update_status( + redis_conn: &mut redis::aio::MultiplexedConnection, + job_id: &str, + status: JobStatus, + ) -> Result<(), JobError> { + let now = Utc::now(); + let job_key = format!("hero:job:{}", job_id); + + let _: () = redis_conn.hset_multiple(&job_key, &[ + ("status", status.as_str()), + ("updated_at", &now.to_rfc3339()), + ]).await + .map_err(|e| JobError::Redis(e))?; + Ok(()) + } + + /// Set job result in Redis using default client + pub async fn set_result( + redis_conn: &mut redis::aio::MultiplexedConnection, + job_id: &str, + result: &str, + ) -> Result<(), JobError> { + let job_key = format!("hero:job:{}", job_id); + let now = Utc::now(); + + let _: () = redis_conn.hset_multiple(&job_key, &[ + ("result", result), + ("status", JobStatus::Finished.as_str()), + ("updated_at", &now.to_rfc3339()), + ]).await + .map_err(|e| JobError::Redis(e))?; + Ok(()) + } + + /// Set job error in Redis using default client + pub async fn set_error( + redis_conn: &mut redis::aio::MultiplexedConnection, + job_id: &str, + error: &str, + ) -> Result<(), JobError> { + let job_key = format!("hero:job:{}", job_id); + let now = Utc::now(); + + let _: () = redis_conn.hset_multiple(&job_key, &[ + ("error", error), + ("status", JobStatus::Error.as_str()), + ("updated_at", &now.to_rfc3339()), + ]).await + .map_err(|e| JobError::Redis(e))?; + Ok(()) + } + + /// Delete job from Redis using default client + pub async fn delete( + redis_conn: &mut redis::aio::MultiplexedConnection, + job_id: &str, + ) -> Result<(), JobError> { + let job_key = format!("hero:job:{}", job_id); + let _: () = redis_conn.del(&job_key).await + .map_err(|e| JobError::Redis(e))?; + Ok(()) + } + +} + +/// Builder for constructing job execution requests. +pub struct JobBuilder { + caller_id: String, + context_id: String, + payload: String, + runner: String, + executor: String, + timeout: u64, // timeout in seconds + env_vars: HashMap, +} + +impl JobBuilder { + pub fn new() -> Self { + Self { + caller_id: "".to_string(), + context_id: "".to_string(), + payload: "".to_string(), + runner: "".to_string(), + executor: "".to_string(), + timeout: 300, // 5 minutes default + env_vars: HashMap::new(), + } + } + + /// Set the caller ID for this job + pub fn caller_id(mut self, caller_id: &str) -> Self { + self.caller_id = caller_id.to_string(); + self + } + + /// Set the context ID for this job + pub fn context_id(mut self, context_id: &str) -> Self { + self.context_id = context_id.to_string(); + self + } + + /// Set the payload (script content) for this job + pub fn payload(mut self, payload: &str) -> Self { + self.payload = payload.to_string(); + self + } + + /// Set the runner name for this job + pub fn runner(mut self, runner: &str) -> Self { + self.runner = runner.to_string(); + self + } + + /// Set the executor for this job + pub fn executor(mut self, executor: &str) -> Self { + self.executor = executor.to_string(); + self + } + + /// Set the timeout for job execution (in seconds) + pub fn timeout(mut self, timeout: u64) -> Self { + self.timeout = timeout; + self + } + + /// Set a single environment variable + pub fn env_var(mut self, key: &str, value: &str) -> Self { + self.env_vars.insert(key.to_string(), value.to_string()); + self + } + + /// Set multiple environment variables from a HashMap + pub fn env_vars(mut self, env_vars: HashMap) -> Self { + self.env_vars = env_vars; + self + } + + /// Clear all environment variables + pub fn clear_env_vars(mut self) -> Self { + self.env_vars.clear(); + self + } + + /// Build the job + pub fn build(self) -> Result { + if self.caller_id.is_empty() { + return Err(JobError::InvalidData("caller_id is required".to_string())); + } + if self.context_id.is_empty() { + return Err(JobError::InvalidData("context_id is required".to_string())); + } + if self.payload.is_empty() { + return Err(JobError::InvalidData("payload is required".to_string())); + } + if self.runner.is_empty() { + return Err(JobError::InvalidData("runner is required".to_string())); + } + if self.executor.is_empty() { + return Err(JobError::InvalidData("executor is required".to_string())); + } + + let mut job = Job::new( + self.caller_id, + self.context_id, + self.payload, + self.runner, + self.executor, + ); + + job.timeout = self.timeout; + job.env_vars = self.env_vars; + + Ok(job) + } +} + +impl Default for JobBuilder { + fn default() -> Self { + Self::new() + } +}