diff --git a/.gitignore b/.gitignore index 2087673..cb42a03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .idea/ /target config_local.json5 + +# MacOS +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 80dd51c..4de90fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,27 +6,12 @@ version = 4 name = "action_macros" version = "0.1.0" dependencies = [ - "convert_case 0.7.1", + "convert_case 0.8.0", "proc-macro2", "quote", "syn", ] -[[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 = "aho-corasick" version = "1.1.3" @@ -47,9 +32,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -62,9 +47,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -77,29 +62,29 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arraydeque" @@ -119,9 +104,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -140,21 +125,6 @@ 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 0.52.6", -] - [[package]] name = "base64" version = "0.21.7" @@ -169,9 +139,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] @@ -192,7 +162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata", "serde", ] @@ -210,27 +180,27 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" -version = "1.1.10" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" [[package]] name = "cargo-config2" -version = "0.1.35" +version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f307d010782c2a4066cc5125ba8c6b68db926b3a1bb82bd6d0b38950c6d4815" +checksum = "3795d3a48839a46854805f56c8fe9c558f10804bcf57df53925ca843d87c788f" dependencies = [ "serde", "serde_derive", "toml", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "cargo-llvm-cov" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecc469e8c1be87be84e3e3c32bcc92eedca5bb57d1e7f606e10154f564a1f8d" +checksum = "42236fed339535379c8671218bd8050ddf2b937cb16f6012b44b6cf993ce7c9c" dependencies = [ "anyhow", "camino", @@ -255,18 +225,19 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.30" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" @@ -298,9 +269,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.13" +version = "0.15.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1eb4fb07bc7f012422df02766c7bd5971effb894f573865642f06fa3265440" +checksum = "180e549344080374f9b32ed41bf3b6b57885ff6a289367b3dbc10eea8acc1918" dependencies = [ "async-trait", "convert_case 0.6.0", @@ -308,10 +279,11 @@ dependencies = [ "pathdiff", "ron", "rust-ini", - "serde", + "serde-untagged", + "serde_core", "serde_json", "toml", - "winnow 0.7.12", + "winnow 0.7.13", "yaml-rust2", ] @@ -346,9 +318,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] @@ -407,9 +379,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5" +checksum = "59c9b8bdf64ee849747c1b12eb861d21aa47fa161564f48332f1afe2373bf899" dependencies = [ "ctor-proc-macro", "dtor", @@ -417,9 +389,9 @@ dependencies = [ [[package]] name = "ctor-proc-macro" -version = "0.0.5" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" [[package]] name = "diff" @@ -459,24 +431,24 @@ dependencies = [ [[package]] name = "dtor" -version = "0.0.6" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" dependencies = [ "dtor-proc-macro", ] [[package]] name = "dtor-proc-macro" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" [[package]] name = "duct" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6ce170a0e8454fa0f9b0e5ca38a6ba17ed76a50916839d217eb5357e05cdfde" +checksum = "d7478638a31d1f1f3d6c9f5e57c76b906a04ac4879d6fd0fb6245bc88f73fd0b" dependencies = [ "libc", "os_pipe", @@ -495,9 +467,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", ] @@ -520,14 +492,25 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -538,16 +521,22 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "fnv" version = "1.0.7" @@ -577,18 +566,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" -version = "3.1.1" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683" +checksum = "6ad492b2cf1d89d568a43508ab24f98501fe03f2f31c01e1d0fe7366a71745d2" dependencies = [ "autocfg", ] @@ -690,9 +679,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -706,38 +695,32 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -760,20 +743,26 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -788,6 +777,7 @@ dependencies = [ "cron", "ctor", "futures", + "humantime-serde", "mockito", "pretty_assertions", "reqwest", @@ -849,15 +839,32 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -865,6 +872,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -904,9 +912,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64 0.22.1", "bytes", @@ -920,7 +928,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2", "system-configuration", "tokio", "tower-service", @@ -1040,9 +1048,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1061,23 +1069,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.4", -] - -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags", - "cfg-if", - "libc", + "hashbrown 0.16.0", ] [[package]] @@ -1110,9 +1107,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -1155,15 +1152,15 @@ checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", @@ -1172,9 +1169,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1184,34 +1181,33 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -1219,24 +1215,15 @@ 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" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1282,21 +1269,20 @@ dependencies = [ [[package]] name = "normpath" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -1308,15 +1294,6 @@ 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" @@ -1331,20 +1308,20 @@ checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "opener" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771b9704f8cd8b424ec747a320b30b47517a6966ba2c7da90047c16f4a962223" +checksum = "cb9024962ab91e00c89d2a14352a8d0fc1a64346bf96f1839b45c09149564e47" dependencies = [ "bstr", "normpath", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" dependencies = [ "bitflags", "cfg-if", @@ -1374,9 +1351,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" dependencies = [ "cc", "libc", @@ -1396,25 +1373,19 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] -[[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.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1422,15 +1393,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -1441,26 +1412,25 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", - "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" dependencies = [ "pest", "pest_generator", @@ -1468,9 +1438,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" dependencies = [ "pest", "pest_meta", @@ -1481,9 +1451,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" dependencies = [ "pest", "sha2", @@ -1529,9 +1499,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -1557,18 +1527,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1584,9 +1554,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1653,61 +1623,46 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "relative-path" @@ -1717,9 +1672,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", @@ -1786,21 +1741,20 @@ dependencies = [ [[package]] name = "rstest" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" dependencies = [ "futures-timer", "futures-util", "rstest_macros", - "rustc_version", ] [[package]] name = "rstest_macros" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" dependencies = [ "cfg-if", "glob", @@ -1816,9 +1770,9 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7295b7ce3bf4806b419dc3420745998b447178b7005e2011947b38fc5aa6791" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" dependencies = [ "cfg-if", "ordered-multimap", @@ -1826,9 +1780,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" @@ -1841,22 +1795,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" dependencies = [ "once_cell", "rustls-pki-types", @@ -1876,9 +1830,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -1887,9 +1841,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ruzstd" @@ -1914,11 +1868,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1942,9 +1896,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -1952,24 +1906,46 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +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", @@ -1978,23 +1954,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2036,15 +2013,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ "libc", - "sigchld", "windows-sys 0.60.2", ] [[package]] name = "shared_thread" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a6f98357c6bb0ebace19b22220e5543801d9de90ffe77f8abb27c056bac064" +checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" [[package]] name = "shell-escape" @@ -2058,32 +2034,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "sigchld" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" -dependencies = [ - "libc", - "os_pipe", - "signal-hook", -] - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -2096,9 +2051,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -2108,29 +2063,19 @@ 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" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "subtle" @@ -2140,9 +2085,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" dependencies = [ "proc-macro2", "quote", @@ -2203,15 +2148,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2247,18 +2192,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2295,29 +2240,26 @@ dependencies = [ [[package]] name = "tokio" -version = "1.46.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.5.10", + "socket2", "tokio-macros", - "windows-sys 0.52.0", + "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", @@ -2347,9 +2289,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -2368,9 +2310,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -2381,50 +2323,45 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "serde", + "serde_core", "serde_spanned", - "toml_datetime 0.7.0", + "toml_datetime", "toml_parser", - "winnow 0.7.12", + "winnow 0.7.13", ] [[package]] name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_datetime" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", - "toml_datetime 0.6.11", - "winnow 0.7.12", + "toml_datetime", + "toml_parser", + "winnow 0.7.13", ] [[package]] name = "toml_parser" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "winnow 0.7.12", + "winnow 0.7.13", ] [[package]] @@ -2517,14 +2454,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -2539,11 +2476,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -2553,9 +2496,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-segmentation" @@ -2571,13 +2514,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2636,31 +2580,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -2672,9 +2617,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -2685,9 +2630,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2695,9 +2640,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -2708,9 +2653,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -2730,45 +2675,23 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.62.2" @@ -2887,7 +2810,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -2908,18 +2840,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2930,9 +2863,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2942,9 +2875,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2954,9 +2887,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2966,9 +2899,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2978,9 +2911,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2990,9 +2923,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -3002,9 +2935,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -3014,9 +2947,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -3029,21 +2962,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -3053,9 +2983,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", "rustix", @@ -3063,9 +2993,9 @@ dependencies = [ [[package]] name = "yaml-rust2" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce2a4ff45552406d02501cea6c18d8a7e50228e7736a872951fe2fe75c91be7" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" dependencies = [ "arraydeque", "encoding_rs", @@ -3104,18 +3034,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -3145,9 +3075,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" @@ -3162,9 +3092,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 1c34f40..4957635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,23 +9,24 @@ tokio = { version = "1", features = ["full"] } tokio-retry = "0.3.0" tokio-stream = { version = "0.1.17", features = ["fs"] } futures = "0.3" # Needed by reqwest's stream feature -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +tracing = "0.1" +tracing-subscriber = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -config = "0.15.9" +config = "0.15" thiserror = "2.0" async-trait = "0.1" cron = "0.15.0" -chrono = "0.4.42" +chrono = "0.4" +humantime-serde = "1.1.1" # Action macros action_macros = { path = "action_macros" } -ctor = "0.4" +ctor = "0.6" [dev-dependencies] -cargo-llvm-cov = "0.6.16" -mockito = "1.7.0" -pretty_assertions = "1.4.1" -rstest = "0.25.0" -test-log = { version = "0.2.17", features = ["trace"] } +cargo-llvm-cov = "0.6" +mockito = "1.7" +pretty_assertions = "1.4" +rstest = "0.26" +test-log = { version = "0.2", features = ["trace"] } diff --git a/action_macros/Cargo.lock b/action_macros/Cargo.lock index eee42b3..5487614 100644 --- a/action_macros/Cargo.lock +++ b/action_macros/Cargo.lock @@ -14,36 +14,36 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "syn" -version = "2.0.98" +version = "2.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" dependencies = [ "proc-macro2", "quote", diff --git a/action_macros/Cargo.toml b/action_macros/Cargo.toml index b6ff387..05c2f64 100644 --- a/action_macros/Cargo.toml +++ b/action_macros/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" proc-macro = true [dependencies] -quote = "1.0.38" -syn = "2.0.98" -proc-macro2 = "1.0.93" -convert_case = "0.7.1" +quote = "1.0.41" +syn = "2.0.107" +proc-macro2 = "1.0.101" +convert_case = "0.8.0" diff --git a/src/execute_flows.rs b/src/execute_flows.rs index 0ef0e20..2df7fd5 100644 --- a/src/execute_flows.rs +++ b/src/execute_flows.rs @@ -4,36 +4,35 @@ use crate::flow_engine; use crate::flow_engine::flow::Flow; use crate::flow_engine::property_value::PropertyValue; use crate::flow_engine::{Context, FlowEngineError, FlowExecutionReport}; +use crate::scheduler::SchedulerCommand; use crate::store::StoreSnapshot; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; use std::collections::HashMap; use std::sync::Arc; +use tokio::sync::mpsc::Sender; use tracing::{instrument, warn}; type CommandMap = HashMap>; +#[instrument(skip_all, fields(flow = flow.name(), node_id = node_id.as_deref().unwrap_or("")))] +pub async fn execute_flow(flow: Arc, node_id: Option, snapshot: StoreSnapshot, tx: Sender) { + let context = Context::new(snapshot.clone()); + let result = flow_engine::execute(&flow, node_id, &context, tx).await; + + let command_map = merge_command_maps(vec![result]); + dispatch_commands(&snapshot, command_map).await; +} + #[instrument(skip_all)] -pub async fn execute_flows(flows: &[Flow], snapshot: StoreSnapshot) { +pub async fn execute_flows(flows: Vec>, snapshot: StoreSnapshot, tx: Sender) { let context = Context::new(snapshot.clone()); - let results = FuturesUnordered::from_iter(flows.iter().map(|flow| async { flow_engine::execute(flow, &context).await })) + let results = FuturesUnordered::from_iter(flows.iter().map(|flow| async { flow_engine::execute(flow, None, &context, tx.clone()).await })) .collect::>() .await; let command_map = merge_command_maps(results); - for (device_id, properties) in command_map { - if let Some(device) = snapshot.devices.get(&device_id) { - if let Some(controller) = device.controller_id.and_then(|controller_id| controller_registry::get(controller_id)) { - let command = Command::ControlDevice { - device: device.clone(), - property: Arc::new(properties), - }; - controller.execute(command).await; - } else { - warn!(device_id, "⚠️ Device '{}' is not tied to a controller", device.name); - } - } - } + dispatch_commands(&snapshot, command_map).await; } fn merge_command_maps(reports: Vec>) -> CommandMap { @@ -51,6 +50,22 @@ fn merge_command_maps(reports: Vec> merged_map } +async fn dispatch_commands(snapshot: &StoreSnapshot, command_map: CommandMap) { + for (device_id, properties) in command_map { + if let Some(device) = snapshot.devices.get(&device_id) { + if let Some(controller) = device.controller_id.and_then(|controller_id| controller_registry::get(controller_id)) { + let command = Command::ControlDevice { + device: device.clone(), + property: Arc::new(properties), + }; + controller.execute(command).await; + } else { + warn!(device_id, "⚠️ Device '{}' is not tied to a controller", device.name); + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/flow_engine/engine.rs b/src/flow_engine/engine.rs index b690e4d..8a3071e 100644 --- a/src/flow_engine/engine.rs +++ b/src/flow_engine/engine.rs @@ -1,17 +1,20 @@ -use crate::flow_engine::Value; use crate::flow_engine::context::Context; use crate::flow_engine::expression::{ExpressionError, evaluate}; use crate::flow_engine::flow::{Flow, FlowNode, FlowNodeKind}; use crate::flow_engine::scope::Scope; +use crate::flow_engine::{SchedulerCommand, Value}; +use ExecuteNodeResult::*; use std::any::Any; use std::collections::HashMap; use std::time::Duration; use thiserror::Error; +use tokio::sync::mpsc::Sender; +use tokio::sync::mpsc::error::SendError; use tokio::time::Instant; use tracing::{debug, info, instrument, trace, warn}; #[instrument(fields(flow = flow.name()), skip_all)] -pub async fn execute(flow: &Flow, context: &Context) -> Result { +pub async fn execute(flow: &Flow, node_id: Option, context: &Context, tx: Sender) -> Result { debug!("⚖️ Evaluating trigger condition for flow..."); let result = evaluate(flow.trigger(), context); match result { @@ -30,9 +33,30 @@ pub async fn execute(flow: &Flow, context: &Context) -> Result Some(node), + End => None, + Sleep { duration, next } => { + tx.send(SchedulerCommand::ScheduleOnce { + flow_id: flow.id().to_string(), + node_id: next.id().to_string(), + delay: duration, + }) + .await?; + None + } + } } let duration = Instant::now() - start; @@ -42,7 +66,7 @@ pub async fn execute(flow: &Flow, context: &Context) -> Result(node: &'a FlowNode, context: &Context, scope: &mut Scope) -> Result, FlowEngineError> { +async fn execute_node<'a>(node: &'a FlowNode, context: &Context, scope: &mut Scope) -> Result, FlowEngineError> { trace!("{:?}", node); let next_flow_link = match node.kind() { @@ -57,18 +81,36 @@ async fn execute_node<'a>(node: &'a FlowNode, context: &Context, scope: &mut Sco let next_node = next_flow_link.ok_or_else(|| FlowEngineError::MissingOutgoingNode(node.id().to_owned()))?.node(); debug!("Next node: {}", next_node.id()); + + if let FlowNodeKind::Sleep(duration) = node.kind() { + return Ok(Sleep { + duration: *duration, + next: next_node, + }); + } + match next_node.kind() { - FlowNodeKind::End => Ok(None), - _ => Ok(Some(next_node)), + FlowNodeKind::End => Ok(End), + _ => Ok(Next(next_node)), } } +enum ExecuteNodeResult<'a> { + Next(&'a FlowNode), + End, + Sleep { duration: Duration, next: &'a FlowNode }, +} + #[derive(Error, Debug)] pub enum FlowEngineError { #[error("missing outgoing node for node '{0}'")] MissingOutgoingNode(String), #[error("evaluation of the flow trigger failed: {0}")] FailedTriggerEvaluation(ExpressionError), + #[error(transparent)] + FailedScheduleSleepCommand(#[from] SendError), + #[error("missing provided start node '{0}'")] + MissingProvidedStartNode(String), } pub struct FlowExecutionReport { @@ -110,6 +152,7 @@ mod tests { use crate::flow_engine::flow::{ActionFlowNode, FlowLink, FlowNodeKind}; use std::sync::Arc; use test_log::test; + use tokio::sync::mpsc; #[test(tokio::test)] async fn executes_a_flow_with_one_action_node() { @@ -123,30 +166,103 @@ mod tests { ); let start_node = FlowNode::new("startNode".to_string(), vec![FlowLink::new(Arc::new(log_node), None)], FlowNodeKind::Start); - let flow = Flow::new("flow".to_string(), None, None, start_node).unwrap(); + let flow = Flow::new("id".to_string(), "flow".to_string(), None, None, Arc::new(start_node), HashMap::new()).unwrap(); - let result = execute(&flow, &Context::default()).await; + let (scheduler_tx, _scheduler_rx) = mpsc::channel::(32); + let result = execute(&flow, None, &Context::default(), scheduler_tx).await; assert!(result.is_ok()); } #[test(tokio::test)] async fn skips_execution_if_the_trigger_returns_false() { let start_node = FlowNode::new("startNode".to_string(), vec![], FlowNodeKind::Start); - let flow = Flow::new("flow".to_string(), None, Some(Expression::Literal { value: Value::Boolean(false) }), start_node).unwrap(); + let flow = Flow::new( + "id".to_string(), + "flow".to_string(), + None, + Some(Expression::Literal { value: Value::Boolean(false) }), + Arc::new(start_node), + HashMap::new(), + ) + .unwrap(); - let result = execute(&flow, &Context::default()).await; + let (scheduler_tx, _scheduler_rx) = mpsc::channel::(32); + let result = execute(&flow, None, &Context::default(), scheduler_tx).await; assert!(result.is_ok()); let result = result.unwrap(); assert!(result.scope.is_empty()); assert_eq!(result.duration, Duration::ZERO); } + #[test(tokio::test)] + async fn fails_if_the_start_node_id_cannot_be_found() { + let start_node = FlowNode::new("startNode".to_string(), vec![], FlowNodeKind::Start); + let flow = Flow::new("id".to_string(), "flow".to_string(), None, None, Arc::new(start_node), HashMap::new()).unwrap(); + + let (scheduler_tx, _scheduler_rx) = mpsc::channel::(32); + let result = execute(&flow, Some("unknown".to_string()), &Context::default(), scheduler_tx).await; + assert!(matches!(result, Err(FlowEngineError::MissingProvidedStartNode(_)))); + } + #[test(tokio::test)] async fn fails_if_an_outgoing_node_is_missing() { let start_node = FlowNode::new("startNode".to_string(), vec![], FlowNodeKind::Start); - let flow = Flow::new("flow".to_string(), None, None, start_node).unwrap(); + let flow = Flow::new("id".to_string(), "flow".to_string(), None, None, Arc::new(start_node), HashMap::new()).unwrap(); - let result = execute(&flow, &Context::default()).await; + let (scheduler_tx, _scheduler_rx) = mpsc::channel::(32); + let result = execute(&flow, None, &Context::default(), scheduler_tx).await; assert!(matches!(result, Err(FlowEngineError::MissingOutgoingNode(_)))); } + + #[test(tokio::test)] + async fn sends_a_schedule_once_command_for_a_sleep_node() { + let end_node = FlowNode::new("end_node".to_string(), vec![], FlowNodeKind::End); + + let sleep_node = FlowNode::new( + "sleep_node".to_string(), + vec![FlowLink::new(Arc::new(end_node), None)], + FlowNodeKind::Sleep(Duration::from_secs(42)), + ); + + let start_node = FlowNode::new("startNode".to_string(), vec![FlowLink::new(Arc::new(sleep_node), None)], FlowNodeKind::Start); + let flow = Flow::new("id".to_string(), "flow".to_string(), None, None, Arc::new(start_node), HashMap::new()).unwrap(); + + let (scheduler_tx, mut scheduler_rx) = mpsc::channel::(32); + + execute(&flow, None, &Context::default(), scheduler_tx).await.unwrap(); + let received_command = scheduler_rx.recv().await; + if let Some(SchedulerCommand::ScheduleOnce { flow_id, node_id, delay }) = received_command { + assert_eq!(flow_id, "id"); + assert_eq!(node_id, "end_node"); + assert_eq!(delay, Duration::from_secs(42)); + } else { + panic!("Expected ScheduleOnce command"); + } + } + + #[test(tokio::test)] + async fn resumes_execution_at_the_specified_node_id() { + let end_node = Arc::new(FlowNode::new("end_node".to_string(), vec![], FlowNodeKind::End)); + + let sleep_node = Arc::new(FlowNode::new( + "sleep_node".to_string(), + vec![FlowLink::new(end_node.clone(), None)], + FlowNodeKind::Sleep(Duration::from_secs(42)), + )); + + let start_node = Arc::new(FlowNode::new("startNode".to_string(), vec![FlowLink::new(sleep_node.clone(), None)], FlowNodeKind::Start)); + let nodes_by_id = HashMap::from([ + (start_node.id().to_string(), start_node.clone()), + (sleep_node.id().to_string(), sleep_node.clone()), + (end_node.id().to_string(), end_node.clone()), + ]); + let flow = Flow::new("id".to_string(), "flow".to_string(), None, None, start_node, nodes_by_id).unwrap(); + + let (scheduler_tx, mut scheduler_rx) = mpsc::channel::(32); + let result = execute(&flow, Some("end_node".to_string()), &Context::default(), scheduler_tx).await.unwrap(); + + // Ensure that nothing was scheduled + assert!(scheduler_rx.try_recv().is_err(), "Expected no scheduler commands to be sent"); + assert!(result.scope.is_empty()); + } } diff --git a/src/flow_engine/expression.rs b/src/flow_engine/expression.rs index 1cc3508..2ea5b76 100644 --- a/src/flow_engine/expression.rs +++ b/src/flow_engine/expression.rs @@ -92,7 +92,7 @@ pub fn evaluate(expression: &Expression, context: &Context) -> Result match (evaluate(expression, context)?) { + Not { expression } => match evaluate(expression, context)? { Value::Boolean(b) => Ok(Value::Boolean(!b)), _ => Err(ExpressionError::UnaryOperandTypeMismatch { operand: "Not", diff --git a/src/flow_engine/flow.rs b/src/flow_engine/flow.rs index d0aebae..615bab0 100644 --- a/src/flow_engine/flow.rs +++ b/src/flow_engine/flow.rs @@ -1,30 +1,47 @@ use crate::flow_engine::Expression::Literal; use crate::flow_engine::action::Action; use crate::flow_engine::{Expression, Value}; +use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; +use std::time::Duration; -#[derive(Debug)] +#[cfg_attr(not(test), derive(Debug))] pub struct Flow { + id: String, name: String, schedule: Option, trigger: Expression, - start_node: FlowNode, + start_node: Arc, + nodes_by_id: HashMap>, } impl Flow { - pub fn new(name: String, schedule: Option, trigger: Option, start_node: FlowNode) -> Result { + pub fn new( + id: String, + name: String, + schedule: Option, + trigger: Option, + start_node: Arc, + nodes_by_id: HashMap>, + ) -> Result { match start_node.kind { FlowNodeKind::Start => Ok(Flow { + id, name, schedule, trigger: trigger.unwrap_or(Literal { value: Value::Boolean(true) }), start_node, + nodes_by_id, }), _ => Err("start_node must be of type FlowNodeKind::Start".to_string()), } } + pub fn id(&self) -> &str { + &self.id + } + pub fn name(&self) -> &str { &self.name } @@ -40,6 +57,10 @@ impl Flow { pub fn schedule(&self) -> Option<&str> { self.schedule.as_deref() } + + pub fn node_by_id(&self, id: &str) -> Option<&FlowNode> { + self.nodes_by_id.get(id).map(|node| node.as_ref()) + } } #[derive(Debug)] @@ -89,6 +110,7 @@ pub enum FlowNodeKind { Start, End, Action(ActionFlowNode), + Sleep(Duration), } #[derive(Debug)] diff --git a/src/flow_engine/mod.rs b/src/flow_engine/mod.rs index 69931fc..da8b531 100644 --- a/src/flow_engine/mod.rs +++ b/src/flow_engine/mod.rs @@ -5,6 +5,7 @@ mod engine; mod expression; pub mod flow; pub mod property_value; +pub mod scheduler; mod scope; pub use context::Context; @@ -12,3 +13,4 @@ pub use engine::FlowEngineError; pub use engine::FlowExecutionReport; pub use engine::execute; pub use expression::{Expression, Value}; +pub use scheduler::{SchedulerCommand, scheduler}; diff --git a/src/flow_engine/scheduler.rs b/src/flow_engine/scheduler.rs new file mode 100644 index 0000000..6dd76c8 --- /dev/null +++ b/src/flow_engine/scheduler.rs @@ -0,0 +1,87 @@ +use crate::execute_flows::{execute_flow, execute_flows}; +use crate::flow_registry::FlowRegistry; +use crate::store::StoreSnapshot; +use chrono::Utc; +use cron::Schedule; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::sync::watch::Receiver as WatchReceiver; +use tokio::time::{Instant, sleep_until}; +use tracing::{debug, error, info, instrument, warn}; + +#[derive(Debug)] +pub enum SchedulerCommand { + Schedule { flow_id: String }, + ScheduleOnce { flow_id: String, node_id: String, delay: Duration }, +} + +#[instrument(skip_all)] +pub async fn scheduler(tx: Sender, mut rx: Receiver, notifier_rx: WatchReceiver, flow_registry: Arc) { + while let Some(cmd) = rx.recv().await { + match cmd { + SchedulerCommand::Schedule { flow_id } => { + let Some(flow) = flow_registry.by_id(&flow_id) else { + warn!("🕗 Scheduling flow '{}'... failed, flow not found", flow_id); + continue; + }; + + let flow_name = flow.name().to_string(); + debug!("🕗 Scheduling flow '{}'...", flow_name); + + if flow.schedule().is_none() { + error!("🕗 Scheduling flow '{}'... failed, not a scheduled flow", flow.name()); + continue; + } + + let cron = flow.schedule().unwrap().to_string(); // Safe because of the match guard + let schedule = match Schedule::from_str(&cron) { + Ok(schedule) => schedule, + Err(_e) => { + warn!("🕗 Scheduling flow '{}'... failed, invalid cron expression '{}'", flow.name(), cron); + continue; + } + }; + + // Job loop + let notifier_rx_clone = notifier_rx.clone(); + let tx_clone = tx.clone(); + tokio::spawn(async move { + for datetime in schedule.upcoming(Utc) { + let duration = datetime.signed_duration_since(Utc::now()); + if duration.num_milliseconds() < 0 { + continue; // Already passed + } + + let scheduled_instant = Instant::now() + Duration::from_millis(duration.num_milliseconds() as u64); + sleep_until(scheduled_instant).await; + + debug!(cron, "🕗 Running scheduled flow '{}'...", flow.name()); + let snapshot = notifier_rx_clone.borrow().clone(); + execute_flows(vec![flow.clone()], snapshot, tx_clone.clone()).await; + } + }); + info!("🕗 Scheduling flow '{}'... OK", flow_name); + } + SchedulerCommand::ScheduleOnce { flow_id, node_id, delay } => { + let Some(flow) = flow_registry.by_id(&flow_id) else { + warn!("🕗 Scheduling flow '{}'... failed, flow not found", flow_id); + return; + }; + + debug!("🕗 Scheduling flow '{}' to run node '{}' after {:?}... OK", flow_id, node_id, delay); + let notifier_rx_clone = notifier_rx.clone(); + let tx_clone = tx.clone(); + tokio::spawn(async move { + let scheduled_instant = Instant::now() + Duration::from_millis(delay.as_millis() as u64); + sleep_until(scheduled_instant).await; + + debug!("🕗 Waking up flow '{}'...", flow.name()); + let snapshot = notifier_rx_clone.borrow().clone(); + execute_flow(flow, Some(node_id), snapshot, tx_clone.clone()).await; + }); + } + } + } +} diff --git a/src/flow_loader/factory.rs b/src/flow_loader/factory.rs index de70466..6fbf4a8 100644 --- a/src/flow_loader/factory.rs +++ b/src/flow_loader/factory.rs @@ -25,13 +25,14 @@ pub fn from_json(json: &str) -> Result { nodes_to_visit.reserve(nodes.len()); // Reserve capacity to avoid multiple reallocations let mut flow_node_map: HashMap> = HashMap::new(); - let mut start_node: Option = None; + let mut start_node: Option> = None; while let Some(serialized_node) = nodes_to_visit.pop_back() { let incoming_nodes: Vec = nodes._extract_if(|node| match node { SerializedFlowNode::StartNode(node) => node.outgoing_node == serialized_node.id(), SerializedFlowNode::EndNode(_) => false, SerializedFlowNode::ActionNode(node) => node.outgoing_node == serialized_node.id(), + SerializedFlowNode::SleepNode(node) => node.outgoing_node == serialized_node.id(), }); if !matches!(serialized_node, SerializedFlowNode::StartNode(_)) && incoming_nodes.is_empty() { @@ -49,12 +50,14 @@ pub fn from_json(json: &str) -> Result { let outgoing_nodes = map_outgoing_nodes(&serialized_node, &flow_node_map)?; let node = to_flow_node(serialized_node, outgoing_nodes); - if matches!(node.kind(), FlowNodeKind::Start) { - // Take ownership of the node as the start node is the last node to traverse - start_node = Some(node); - } else { - flow_node_map.insert(node.id().to_owned(), Arc::new(node)); + let node_id = node.id().to_owned(); + let node_arc = Arc::new(node); + + if matches!(node_arc.kind(), FlowNodeKind::Start) { + start_node = Some(node_arc.clone()); } + + flow_node_map.insert(node_id, node_arc); } if !nodes.is_empty() { @@ -63,7 +66,15 @@ pub fn from_json(json: &str) -> Result { }); } - let flow = Flow::new(flow.name, flow.schedule, flow.trigger, start_node.ok_or_else(|| FlowFactoryError::MissingStartNode)?).expect("Flow creation failed"); + let flow = Flow::new( + flow.id, + flow.name, + flow.schedule, + flow.trigger, + start_node.ok_or_else(|| FlowFactoryError::MissingStartNode)?, + flow_node_map, + ) + .expect("Flow creation failed"); Ok(flow) } @@ -86,6 +97,7 @@ fn to_flow_node(serialized_node: SerializedFlowNode, outgoing_nodes: Vec FlowNode::new(node.id, outgoing_nodes, FlowNodeKind::Start), SerializedFlowNode::EndNode(node) => FlowNode::new(node.id, outgoing_nodes, FlowNodeKind::End), SerializedFlowNode::ActionNode(node) => FlowNode::new(node.id, outgoing_nodes, FlowNodeKind::Action(ActionFlowNode::new(node.action))), + SerializedFlowNode::SleepNode(node) => FlowNode::new(node.id, outgoing_nodes, FlowNodeKind::Sleep(node.duration)), } } @@ -138,6 +150,7 @@ mod tests { use crate::flow_engine::action::{ControlDeviceAction, LogAction}; use crate::flow_engine::property_value::PropertyValue::SetBooleanValue; use pretty_assertions::assert_eq; + use std::time::Duration; #[tokio::test] async fn returns_an_error_if_an_unknown_node_type_is_found() { @@ -148,7 +161,7 @@ mod tests { #[tokio::test] async fn returns_an_error_if_no_start_node_is_found() { - let json = r#"{ "name": "flow", "nodes": [] }"#; + let json = r#"{ "id": "id", "name": "flow", "nodes": [] }"#; let result = from_json(json); assert!(matches!(result, Err(FlowFactoryError::MissingStartNode))); } @@ -189,7 +202,15 @@ mod tests { let end_node = FlowNode::new("endNode".to_string(), vec![], FlowNodeKind::End); let start_node = FlowNode::new("startNode".to_string(), vec![FlowLink::new(Arc::new(end_node), None)], FlowNodeKind::Start); - let expected = Flow::new("emptyFlow".to_string(), None, None, start_node).unwrap(); + let expected = Flow::new( + "01K7KK65D87SZGGZE7VB8QYT20".to_string(), + "emptyFlow".to_string(), + None, + None, + Arc::new(start_node), + HashMap::new(), + ) + .unwrap(); assert_eq!(format!("{:#?}", flow), format!("{:#?}", expected)); } @@ -208,7 +229,15 @@ mod tests { let start_node = FlowNode::new("startNode".to_string(), vec![FlowLink::new(Arc::new(action_node), None)], FlowNodeKind::Start); - let expected = Flow::new("logFlow".to_string(), None, None, start_node).unwrap(); + let expected = Flow::new( + "01K7KK6H5R7Y72QJEJSJQCKMRQ".to_string(), + "logFlow".to_string(), + None, + None, + Arc::new(start_node), + HashMap::new(), + ) + .unwrap(); assert_eq!(format!("{:#?}", flow), format!("{:#?}", expected)); } @@ -230,7 +259,56 @@ mod tests { let start_node = FlowNode::new("startNode".to_string(), vec![FlowLink::new(Arc::new(action_node), None)], FlowNodeKind::Start); - let expected = Flow::new("controlDeviceFlow".to_string(), None, None, start_node).unwrap(); + let expected = Flow::new( + "01K7KK5FC54SN8D4QYVNEGFYG4".to_string(), + "controlDeviceFlow".to_string(), + None, + None, + Arc::new(start_node), + HashMap::new(), + ) + .unwrap(); assert_eq!(format!("{:#?}", flow), format!("{:#?}", expected)); } + + #[tokio::test] + async fn creates_a_flow_with_a_sleep_node() { + let json = include_str!("../../tests/resources/flows/sleepFlow.json"); + let flow = from_json(json).unwrap(); + + let end_node = FlowNode::new("endNode".to_string(), vec![], FlowNodeKind::End); + + let sleep_node = FlowNode::new( + "sleepNode".to_string(), + vec![FlowLink::new(Arc::new(end_node), None)], + FlowNodeKind::Sleep(Duration::from_secs(3907)), + ); + + let start_node = FlowNode::new("startNode".to_string(), vec![FlowLink::new(Arc::new(sleep_node), None)], FlowNodeKind::Start); + + let expected = Flow::new( + "01K7KK7E6GG26XZZDXSGFZCWQ4".to_string(), + "sleepFlow".to_string(), + None, + None, + Arc::new(start_node), + HashMap::new(), + ) + .unwrap(); + assert_eq!(format!("{:#?}", flow), format!("{:#?}", expected)); + } +} + +#[cfg(test)] +impl std::fmt::Debug for Flow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Ignores the nodes_by_id field as the order is not deterministic and all nodes are reachable by start node + f.debug_struct("Flow") + .field("id", &self.id()) + .field("name", &self.name()) + .field("schedule", &self.schedule()) + .field("trigger", &self.trigger()) + .field("start_node", &self.start_node()) + .finish() + } } diff --git a/src/flow_loader/serialized_flow.rs b/src/flow_loader/serialized_flow.rs index 9ec8160..afb86ef 100644 --- a/src/flow_loader/serialized_flow.rs +++ b/src/flow_loader/serialized_flow.rs @@ -1,9 +1,11 @@ use crate::flow_engine::Expression; use crate::flow_engine::action::Action; use serde::Deserialize; +use std::time::Duration; #[derive(Debug, Deserialize)] pub struct SerializedFlow { + pub(crate) id: String, pub(crate) name: String, pub(crate) schedule: Option, pub(crate) trigger: Option, @@ -16,6 +18,7 @@ pub enum SerializedFlowNode { StartNode(SerializedStartFlowNode), EndNode(SerializedEndFlowNode), ActionNode(SerializedActionFlowNode), + SleepNode(SerializedSleepFlowNode), } impl SerializedFlowNode { @@ -24,6 +27,7 @@ impl SerializedFlowNode { SerializedFlowNode::StartNode(node) => &node.id, SerializedFlowNode::EndNode(node) => &node.id, SerializedFlowNode::ActionNode(node) => &node.id, + SerializedFlowNode::SleepNode(node) => &node.id, } } @@ -32,6 +36,7 @@ impl SerializedFlowNode { SerializedFlowNode::StartNode(node) => Some(&node.outgoing_node), SerializedFlowNode::EndNode(_) => None, SerializedFlowNode::ActionNode(node) => Some(&node.outgoing_node), + SerializedFlowNode::SleepNode(node) => Some(&node.outgoing_node), } } } @@ -56,6 +61,15 @@ pub struct SerializedActionFlowNode { pub(crate) action: Box, } +#[derive(Debug, Deserialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct SerializedSleepFlowNode { + pub(crate) id: String, + pub(crate) outgoing_node: String, + #[serde(with = "humantime_serde")] + pub(crate) duration: Duration, +} + #[cfg(test)] mod tests { use super::*; @@ -70,6 +84,7 @@ mod tests { let flow = serde_json::from_str::(json).unwrap(); let expected = SerializedFlow { + id: "01K7KK6H5R7Y72QJEJSJQCKMRQ".to_string(), name: "logFlow".to_string(), schedule: None, trigger: None, @@ -110,11 +125,31 @@ mod tests { } #[tokio::test] - async fn test_serialized_flow_with_schedule() { - let json = include_str!("../../tests/resources/flows/logFlowWithSchedule.json"); + async fn test_serialized_flow_with_sleep_node() { + let json = include_str!("../../tests/resources/flows/sleepFlow.json"); let flow = serde_json::from_str::(json).unwrap(); - assert_eq!(flow.schedule, Some("*/5 * * * * *".to_string())); - assert_eq!(flow.trigger, None); + let expected = SerializedFlow { + id: "01K7KK7E6GG26XZZDXSGFZCWQ4".to_string(), + name: "sleepFlow".to_string(), + schedule: None, + trigger: None, + nodes: vec![ + SerializedFlowNode::StartNode(SerializedStartFlowNode { + id: "startNode".to_string(), + outgoing_node: "sleepNode".to_string(), + }), + SerializedFlowNode::SleepNode(SerializedSleepFlowNode { + id: "sleepNode".to_string(), + outgoing_node: "endNode".to_string(), + duration: Duration::from_secs(3907), + }), + SerializedFlowNode::EndNode(SerializedEndFlowNode { id: "endNode".to_string() }), + ], + }; + + // As ActionFlowNode's action cannot implement PartialEq, use debug print for comparison + assert_eq!(format!("{:#?}", flow), format!("{:#?}", expected)); + println!("{:?}", flow); } } diff --git a/src/flow_registry.rs b/src/flow_registry.rs new file mode 100644 index 0000000..b47ad47 --- /dev/null +++ b/src/flow_registry.rs @@ -0,0 +1,29 @@ +use crate::flow_engine::flow::Flow; +use std::collections::HashMap; +use std::sync::Arc; + +pub struct FlowRegistry { + flows: Vec>, + by_id: HashMap, +} + +impl FlowRegistry { + pub fn new(flows: Vec) -> Self { + let by_id = flows.iter().enumerate().map(|(index, flow)| (flow.id().to_string(), index)).collect(); + let flow_arcs = flows.into_iter().map(|flow| Arc::new(flow)).collect(); + + Self { flows: flow_arcs, by_id } + } + + pub fn reactive_flows(&self) -> Vec> { + self.flows.iter().filter(|flow| flow.schedule().is_none()).cloned().collect() + } + + pub fn scheduled_flows(&self) -> Vec> { + self.flows.iter().filter(|flow| flow.schedule().is_some()).cloned().collect() + } + + pub fn by_id(&self, id: &str) -> Option> { + self.by_id.get(id).map(|&index| &self.flows[index]).cloned() + } +} diff --git a/src/main.rs b/src/main.rs index bceb47b..1d33ee9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use crate::app_config::AppConfig; use crate::domain::controller_registry; use crate::domain::events::Event; -use crate::scheduler::{SchedulerCommand, scheduler}; +use crate::flow_engine::{SchedulerCommand, scheduler}; +use crate::flow_registry::FlowRegistry; use crate::store::Store; use crate::store_listener::store_listener; use std::sync::Arc; @@ -15,9 +16,9 @@ mod execute_flows; mod extensions; mod flow_engine; mod flow_loader; +mod flow_registry; mod hue; mod property_changed_reducer; -mod scheduler; mod sse; mod store; mod store_listener; @@ -32,19 +33,27 @@ async fn main() -> Result<(), Box> { info!("✅ Loaded configuration"); let flows = flow_loader::load_flows_from(config.flows().directory(), "json").await.unwrap_or_else(|_| Vec::new()); // Errors are already logged in the function - let (scheduled_flows, reactive_flows): (Vec<_>, Vec<_>) = flows.into_iter().partition(|flow| flow.schedule().is_some()); + let flow_registry = Arc::new(FlowRegistry::new(flows)); info!("✅ Loaded flows"); let (tx, rx) = mpsc::channel::(config.core().store_buffer_size()); let mut store = Store::new(rx); let (scheduler_tx, scheduler_rx) = mpsc::channel::(32); + let scheduler_tx_clone = scheduler_tx.clone(); let store_rx = store.notifier(); + let registry_clone = flow_registry.clone(); task::spawn(async move { - scheduler(scheduler_rx, store_rx).await; + scheduler(scheduler_tx_clone, scheduler_rx, store_rx, registry_clone).await; }); - for scheduled_flow in scheduled_flows { - scheduler_tx.send(SchedulerCommand::Schedule(scheduled_flow)).await?; + info!("✅ Started scheduler"); + + for scheduled_flow in flow_registry.scheduled_flows() { + scheduler_tx + .send(SchedulerCommand::Schedule { + flow_id: scheduled_flow.id().to_string(), + }) + .await?; } info!("✅ Scheduled flows"); @@ -55,7 +64,7 @@ async fn main() -> Result<(), Box> { let store_rx = store.notifier(); task::spawn(async move { - store_listener(store_rx, reactive_flows).await; + store_listener(store_rx, flow_registry, scheduler_tx).await; }); info!("✅ Initialized store listener"); @@ -73,7 +82,7 @@ async fn main() -> Result<(), Box> { info!("✅ Discovered all devices"); info!("🔥 {} is up and running", env!("CARGO_PKG_NAME")); - hue::observe(tx.clone(), &hue_client, &config).await?; + hue::observe(tx, &hue_client, &config).await?; match signal::ctrl_c().await { Ok(()) => {} diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs deleted file mode 100644 index a7b94cf..0000000 --- a/src/scheduler/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod scheduler; - -pub use scheduler::{SchedulerCommand, scheduler}; diff --git a/src/scheduler/scheduler.rs b/src/scheduler/scheduler.rs deleted file mode 100644 index af31c10..0000000 --- a/src/scheduler/scheduler.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::execute_flows::execute_flows; -use crate::flow_engine::flow::Flow; -use crate::store::StoreSnapshot; -use chrono::Utc; -use cron::Schedule; -use std::str::FromStr; -use std::time::Duration; -use tokio::sync::mpsc::Receiver; -use tokio::sync::watch::Receiver as WatchReceiver; -use tokio::time::{Instant, sleep_until}; -use tracing::{debug, error, info, instrument, warn}; - -#[derive(Debug)] -pub enum SchedulerCommand { - Schedule(Flow), -} - -#[instrument(skip_all)] -pub async fn scheduler(mut rx: Receiver, notifier_rx: WatchReceiver) { - while let Some(cmd) = rx.recv().await { - match cmd { - SchedulerCommand::Schedule(flow) if flow.schedule().is_some() => { - let flow_name = flow.name().to_string(); - debug!("🕗 Scheduling flow '{}'...", flow_name); - - let cron = flow.schedule().unwrap().to_string(); // Safe because of the match guard - let schedule = match Schedule::from_str(&cron) { - Ok(schedule) => schedule, - Err(_e) => { - warn!("🕗 Scheduling flow '{}'... failed, invalid cron expression '{}'", flow.name(), cron); - continue; - } - }; - - // Job loop - let notifier_rx_clone = notifier_rx.clone(); - tokio::spawn(async move { - for datetime in schedule.upcoming(Utc) { - let duration = datetime.signed_duration_since(Utc::now()); - if duration.num_milliseconds() < 0 { - continue; // Already passed - } - - let scheduled_instant = Instant::now() + Duration::from_millis(duration.num_milliseconds() as u64); - sleep_until(scheduled_instant).await; - - debug!(cron, "🕗 Running scheduled flow '{}'...", flow.name()); - let snapshot = notifier_rx_clone.borrow().clone(); - execute_flows(std::slice::from_ref(&flow), snapshot).await; - } - }); - info!("🕗 Scheduling flow '{}'... OK", flow_name); - } - SchedulerCommand::Schedule(flow) => { - error!("🕗 Scheduling flow '{}'... failed, not a scheduled flow", flow.name()); - } - } - } -} diff --git a/src/store_listener.rs b/src/store_listener.rs index 036e67b..92373df 100644 --- a/src/store_listener.rs +++ b/src/store_listener.rs @@ -1,13 +1,16 @@ use crate::execute_flows::execute_flows; -use crate::flow_engine::flow::Flow; +use crate::flow_registry::FlowRegistry; +use crate::scheduler::SchedulerCommand; use crate::store::StoreSnapshot; +use std::sync::Arc; +use tokio::sync::mpsc::Sender; use tokio::sync::watch::Receiver; use tracing::instrument; #[instrument(skip_all)] -pub async fn store_listener(mut rx: Receiver, flows: Vec) { +pub async fn store_listener(mut rx: Receiver, flow_registry: Arc, scheduler_tx: Sender) { while rx.changed().await.is_ok() { let snapshot: StoreSnapshot = rx.borrow().clone(); - execute_flows(&flows, snapshot).await; + execute_flows(flow_registry.reactive_flows(), snapshot, scheduler_tx.clone()).await; } } diff --git a/tests/resources/flows/controlDeviceFlow.json b/tests/resources/flows/controlDeviceFlow.json index 3cbf426..c6e2594 100644 --- a/tests/resources/flows/controlDeviceFlow.json +++ b/tests/resources/flows/controlDeviceFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KK5FC54SN8D4QYVNEGFYG4", "name": "controlDeviceFlow", "nodes": [ { diff --git a/tests/resources/flows/emptyFlow.json b/tests/resources/flows/emptyFlow.json index 1ec7b80..ad0a010 100644 --- a/tests/resources/flows/emptyFlow.json +++ b/tests/resources/flows/emptyFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KK65D87SZGGZE7VB8QYT20", "name": "emptyFlow", "nodes": [ { diff --git a/tests/resources/flows/invalid/missingEndNodeFlow.json b/tests/resources/flows/invalid/missingEndNodeFlow.json index 5b1c114..8d2105f 100644 --- a/tests/resources/flows/invalid/missingEndNodeFlow.json +++ b/tests/resources/flows/invalid/missingEndNodeFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KKNRMMQZCBRKMM914VK75R", "name": "emptyFlow", "nodes": [ { diff --git a/tests/resources/flows/invalid/multipleStartNodesFlow.json b/tests/resources/flows/invalid/multipleStartNodesFlow.json index 8f51256..fc0bded 100644 --- a/tests/resources/flows/invalid/multipleStartNodesFlow.json +++ b/tests/resources/flows/invalid/multipleStartNodesFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KKNG97SPZHAZEJYKBWRBNG", "name": "flow", "nodes": [ { diff --git a/tests/resources/flows/invalid/unconnectedNodeFlow.json b/tests/resources/flows/invalid/unconnectedNodeFlow.json index 846ab75..36012ba 100644 --- a/tests/resources/flows/invalid/unconnectedNodeFlow.json +++ b/tests/resources/flows/invalid/unconnectedNodeFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KKMWZQ7YNYKDCN9VT8YTCW", "name": "emptyFlow", "nodes": [ { diff --git a/tests/resources/flows/invalid/unknownNodeTypeFlow.json b/tests/resources/flows/invalid/unknownNodeTypeFlow.json index 656a81e..1bfa2b8 100644 --- a/tests/resources/flows/invalid/unknownNodeTypeFlow.json +++ b/tests/resources/flows/invalid/unknownNodeTypeFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KKQ3VZB85PE2HKA4PQWJTX", "name": "emptyFlow", "nodes": [ { diff --git a/tests/resources/flows/invalid/unusedNodesFlow.json b/tests/resources/flows/invalid/unusedNodesFlow.json index 3ea78ae..3c93aa0 100644 --- a/tests/resources/flows/invalid/unusedNodesFlow.json +++ b/tests/resources/flows/invalid/unusedNodesFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KKQAS625TBDHNCHW3TH5M2", "name": "emptyFlow", "nodes": [ { diff --git a/tests/resources/flows/logFlow.json b/tests/resources/flows/logFlow.json index c4c7fad..4efbf84 100644 --- a/tests/resources/flows/logFlow.json +++ b/tests/resources/flows/logFlow.json @@ -1,4 +1,5 @@ { + "id": "01K7KK6H5R7Y72QJEJSJQCKMRQ", "name": "logFlow", "nodes": [ { diff --git a/tests/resources/flows/logFlowWithSchedule.json b/tests/resources/flows/logFlowWithSchedule.json index 116c9fd..5a404cb 100644 --- a/tests/resources/flows/logFlowWithSchedule.json +++ b/tests/resources/flows/logFlowWithSchedule.json @@ -1,4 +1,5 @@ { + "id": "01K7KK6S7VH9ZMY2MXPD4NJJGC", "name": "logFlowWithSchedule", "schedule": "*/5 * * * * *", "nodes": [ diff --git a/tests/resources/flows/logFlowWithTrigger.json b/tests/resources/flows/logFlowWithTrigger.json index 09fc9e6..106c45c 100644 --- a/tests/resources/flows/logFlowWithTrigger.json +++ b/tests/resources/flows/logFlowWithTrigger.json @@ -1,4 +1,5 @@ { + "id": "01K7KK75QJTCKQFD9VYGEDA5P6", "name": "logFlowWithTrigger", "trigger": { "type": "equalTo", diff --git a/tests/resources/flows/sleepFlow.json b/tests/resources/flows/sleepFlow.json new file mode 100644 index 0000000..a1b436e --- /dev/null +++ b/tests/resources/flows/sleepFlow.json @@ -0,0 +1,21 @@ +{ + "id": "01K7KK7E6GG26XZZDXSGFZCWQ4", + "name": "sleepFlow", + "nodes": [ + { + "id": "startNode", + "type": "startNode", + "outgoingNode": "sleepNode" + }, + { + "id": "sleepNode", + "type": "sleepNode", + "outgoingNode": "endNode", + "duration": "1h5m7s" + }, + { + "id": "endNode", + "type": "endNode" + } + ] +}