diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b0353d3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1573 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "logs_tf" +version = "0.1.0" +dependencies = [ + "chrono", + "json", + "reqwest", + "ucore", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ucli" +version = "0.1.0" + +[[package]] +name = "ucore" +version = "0.1.0" +dependencies = [ + "num-derive", + "num-traits", +] + +[[package]] +name = "udb" +version = "0.1.0" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7be94a0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +resolver = "2" + +members = [ + "cli", + "core", + "db", + "logs_tf" +] diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..dfd9c68 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "ucli" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..7d6ddb9 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,4 @@ +fn main() +{ + println!("Hello, world!"); +} diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..1384be5 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ucore" +version = "0.1.0" +edition = "2021" + +[dependencies] +num-derive = "0.4.2" +num-traits = "0.2.19" diff --git a/core/src/class.rs b/core/src/class.rs new file mode 100644 index 0000000..4d78ea7 --- /dev/null +++ b/core/src/class.rs @@ -0,0 +1,85 @@ +use std::fmt; +use std::str::FromStr; + +use num_derive::FromPrimitive; + +pub const NUM_CLASSES: usize = 9; + +/// All TF2 classes. +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] +pub enum Class +{ + Scout, + Soldier, + Pyro, + Demoman, + Heavy, + Engineer, + Medic, + Sniper, + Spy, + Unknown, +} + +impl Class +{ + /// Check if the class is considered a "main" class, which means it is under + /// consideration for main (most) played class during a game. + /// + /// # Returns + /// `true` if the class is main-classeable, currently that includes + /// `Demoman`, `Scout`, `Soldier` and `Medic`. `false` for all other + /// classes. + pub fn is_main_class(self) -> bool + { + matches!( + self, + Self::Demoman | Self::Medic | Self::Scout | Self::Soldier + ) + } +} + +/// When creating identifying a class from a string, the class may be unknown in +/// case the string does not conform to all lowercase string as it is present in +/// the logs.tf API. In that case, this error is thrown, containing the content +/// of the string. +#[derive(Debug)] +pub struct UnknownClassError +{ + class: String, +} + +impl fmt::Display for UnknownClassError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "Unknown class `{}`", &self.class) + } +} + +impl std::error::Error for UnknownClassError {} + +impl FromStr for Class +{ + // Returns the name of the class in case the class is unknown + type Err = UnknownClassError; + + fn from_str(s: &str) -> Result + { + match s { + "demoman" => Ok(Self::Demoman), + "engineer" => Ok(Self::Engineer), + "heavy" | "heavyweapons" => Ok(Self::Heavy), + "medic" => Ok(Self::Medic), + "pyro" => Ok(Self::Pyro), + "scout" => Ok(Self::Scout), + "sniper" => Ok(Self::Sniper), + "soldier" => Ok(Self::Soldier), + "spy" => Ok(Self::Spy), + "unknown" => Ok(Self::Unknown), + unknown => Err(UnknownClassError { + class: unknown.to_string(), + }), + } + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..d2a0d21 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,5 @@ +pub mod class; +pub use class::*; + +pub mod steam_id; +pub use steam_id::*; diff --git a/core/src/steam_id.rs b/core/src/steam_id.rs new file mode 100644 index 0000000..05a5dc5 --- /dev/null +++ b/core/src/steam_id.rs @@ -0,0 +1,264 @@ +//! Handling of steam ids. Since there are multiple versions and the logs.tf API +//! uses steamID64 for lookups but has steamID3s in the log files, a safe +//! conversion and type safety between these two is critical. + +use std::str::FromStr; + +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +const ACCOUNT_INSTANCE_OFFSET_BITS: u64 = 32; +// Account instance is located at ACCOUNT_INSTANCE_OFFSET_BITS and 20 bits long +const ACCOUNT_INSTANCE_MASK: u64 = 0xfffff << ACCOUNT_INSTANCE_OFFSET_BITS; +const ACCOUNT_TYPE_OFFSET_BITS: u64 = 52; +const UNIVERSE_OFFSET_BITS: u64 = 56; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct SteamID +{ + id64: u64, +} + +impl SteamID +{ + /// Create a steam id from its steamID64 representation. + /// + /// The id value is not checked and it is therefore possible to create an + /// invalid steam id with this. + pub const unsafe fn new(id64: u64) -> Self { Self { id64 } } + + /// Create a steam id from its steamID64 representation. + /// + /// Checks if the id is in a sane format. In case it is not, an `Err(())` is + /// returned. + /// + /// # Warning + /// It does not actually make a request to check if there is a profile + /// connected to this steam id, so lookups for the profile may still fail. + pub fn new_checked(id64: u64) -> Result + { + if Self::try_for_universe(id64).is_some() + && Self::try_for_account_type(id64).is_some() + // Check for normal user account, the only one currently supported + && (id64 & ACCOUNT_INSTANCE_MASK == 1 << ACCOUNT_INSTANCE_OFFSET_BITS) + { + Ok(Self { id64 }) + } + else { + Err(()) + } + } + + /// Create a steam id from the parts usually present. The account type will + /// always be set to a user account. + pub fn from_parts(universe: Universe, account_type: AccountType, id: u32) -> Self + { + let mut id64 = 0; + id64 |= id as u64; + + // Assume user account + id64 |= 1 << ACCOUNT_INSTANCE_OFFSET_BITS; + + id64 |= (account_type as u64) << ACCOUNT_TYPE_OFFSET_BITS; + id64 |= (universe as u64) << UNIVERSE_OFFSET_BITS; + + Self { id64 } + } + + fn try_for_universe(id64: u64) -> Option + { + let universe_byte: u8 = + ((id64 & (0xff << UNIVERSE_OFFSET_BITS)) >> UNIVERSE_OFFSET_BITS) as u8; + Universe::from_u8(universe_byte) + } + fn try_for_account_type(id64: u64) -> Option + { + let account_type_nibble: u8 = + ((id64 & (0xf << ACCOUNT_TYPE_OFFSET_BITS)) >> ACCOUNT_TYPE_OFFSET_BITS) as u8; + AccountType::from_u8(account_type_nibble) + } + + /// Get the universe this account is part of. + /// + /// # Panics + /// If there is no valid universe in this steam id, which means that the + /// internal data is corrupt, which is only possible when creating using the + /// unsafe `new` method. + pub fn universe(self) -> Universe + { + Self::try_for_universe(self.id64).expect("Corrupted steam id. Check unsafe `new` calls") + } + + /// Get the type of this account + /// + /// # Panics + /// If the steam id is corrupt. Can only happen with accounts created with + /// unsafe `new` method. + pub fn account_type(self) -> AccountType + { + Self::try_for_account_type(self.id64).expect("Corrupted steam id. Check unsafe `new` calls") + } + + pub fn id64(self) -> u64 { self.id64 } + + pub fn to_id64_string(self) -> String { self.id64.to_string() } + + pub fn to_id3_string(self) -> String + { + let mut res = "[".to_owned(); + res.push(self.account_type().into()); + res.push(':'); + let id = self.id64 as u32; + res += &(id & 1).to_string(); + res.push(':'); + res += &(id >> 1).to_string(); + res.push(']'); + + res + } + + pub fn to_id1_string(self) -> String + { + let mut res = "STEAM_".to_owned(); + res += &(self.universe() as u8).to_string(); + res.push(':'); + let id = self.id64 as u32; + res += &(id & 1).to_string(); + res.push(':'); + res += &(id >> 1).to_string(); + + res + } +} + +impl FromStr for SteamID +{ + type Err = (); + + fn from_str(s: &str) -> Result + { + // Try known conversions + // Starting with steamid64 if it's just a number. + if let Ok(id64) = s.parse::() { + Self::new_checked(id64) + } + // Check for ID3 + else if s.starts_with('[') && s.ends_with(']') { + let parts: Vec<&str> = s.split(':').collect(); + if parts.len() == 3 && parts[0].len() == 2 && parts[1].len() == 1 { + let account_id = parts[2][..parts[2].len() - 1] + .parse::() + .map_err(|_| ())?; + let account_type: AccountType = parts[0].chars().nth(1).unwrap().try_into()?; + let universe: Universe = Universe::Public; + + Ok(Self::from_parts(universe, account_type, account_id)) + } + else { + Err(()) + } + } + // Check for legacy ID format + else if s.starts_with("STEAM_") { + todo!() + } + // Not a known format + else { + Err(()) + } + } +} + +#[derive(Copy, Clone, Debug, FromPrimitive)] +pub enum Universe +{ + Unspecified = 0, + Public = 1, + Beta = 2, + Internal = 3, + Dev = 4, + RC = 5, +} + +#[derive(Copy, Clone, Debug, FromPrimitive)] +pub enum AccountType +{ + Invalid = 0, + Individual = 1, + Multiseat = 2, + GameServer = 3, + AnonGameServer = 4, + Pending = 5, + ContentServer = 6, + Clan = 7, + Chat = 8, + // P2P SuperSeeder ignored + AnonUser = 10, +} + +impl From for char +{ + fn from(value: AccountType) -> Self + { + match value { + AccountType::Invalid => 'I', + AccountType::Individual => 'U', + AccountType::Multiseat => 'M', + AccountType::GameServer => 'G', + AccountType::AnonGameServer => 'A', + AccountType::Pending => 'P', + AccountType::ContentServer => 'C', + AccountType::Clan => 'g', + AccountType::Chat => 'c', + AccountType::AnonUser => 'a', + } + } +} + +impl TryFrom for AccountType +{ + type Error = (); + + fn try_from(value: char) -> Result + { + match value { + 'I' => Ok(Self::Invalid), + 'U' => Ok(Self::Individual), + 'M' => Ok(Self::Multiseat), + 'G' => Ok(Self::GameServer), + 'A' => Ok(Self::AnonGameServer), + 'P' => Ok(Self::Pending), + 'C' => Ok(Self::ContentServer), + 'g' => Ok(Self::Clan), + 'T' | 'L' | 'c' => Ok(Self::Chat), + 'a' => Ok(Self::AnonUser), + _ => Err(()), + } + } +} + +#[cfg(test)] +mod test +{ + use std::str::FromStr; + + use crate::SteamID; + + #[test] + fn from_id3() + { + assert_eq!( + SteamID::from_str("[U:1:71020853]") + .expect("Unable to parse") + .id64(), + 76561198031286581 + ); + + assert_eq!( + SteamID::from_str("[U:1:287181528]") + .expect("Unable to parse") + .id64(), + 76561198247447256 + ); + } +} diff --git a/db/Cargo.toml b/db/Cargo.toml new file mode 100644 index 0000000..cfb0473 --- /dev/null +++ b/db/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "udb" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/db/src/lib.rs b/db/src/lib.rs new file mode 100644 index 0000000..938ad09 --- /dev/null +++ b/db/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { left + right } + +#[cfg(test)] +mod tests +{ + use super::*; + + #[test] + fn it_works() + { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/logs_tf/Cargo.toml b/logs_tf/Cargo.toml new file mode 100644 index 0000000..77503de --- /dev/null +++ b/logs_tf/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "logs_tf" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = "0.4.38" +json = "0.12.4" +reqwest = { version = "0.12.9", features = ["blocking"] } +ucore = { path = "../core" } diff --git a/logs_tf/src/lib.rs b/logs_tf/src/lib.rs new file mode 100644 index 0000000..698d89e --- /dev/null +++ b/logs_tf/src/lib.rs @@ -0,0 +1,83 @@ +pub mod log; +pub mod performance; +pub mod query_error; +pub mod search_params; + +use std::thread; +use std::time::Duration; + +use json::JsonValue; +pub use log::*; +pub use query_error::*; +use reqwest::blocking as reqwest; + +use self::search_params::SearchParams; + +const LOGS_TF_API_BASE: &str = "https://logs.tf/api/v1/log"; + +/// Function that tries to execute something that returns a result. If it does +/// not work the first time, it will keep trying num_retries times until it +/// either returns Ok() or all the tries have been used up. +pub(self) fn keep_trying(action: A, num_retries: u8) -> Result +where + A: Fn() -> Result, +{ + let mut num_tries = 0; + loop { + let res = action(); + num_tries += 1; + + if res.is_ok() || num_tries > num_retries + 1 { + return res; + } + } +} + +/// Sleep for a little time before making a request to logs.tf. The API is very +/// sensitive to quickly making queries to it and will respond with invalid +/// responses otherwise. +pub(self) fn log_delay() { thread::sleep(Duration::from_millis(500)) } + +/// Checks for the `"success": true` field in the json value, which is always +/// set by logs.tf. If `"success": false` is set, it will parse the error and +/// return a `QueryError`. +fn check_json_success(json: &JsonValue) -> QueryResult<()> +{ + let success = json["success"].as_bool().unwrap(); + + if success { + Ok(()) + } + else { + let error = json["error"].as_str().unwrap(); + Err(QueryError::Unsuccessful(error.to_owned())) + } +} + +fn search_logs_once(search_params: &SearchParams) -> QueryResult> +{ + log_delay(); + + let request = reqwest::Client::builder().build()?.get(LOGS_TF_API_BASE); + let request = search_params.add_params_to_request(request); + + let response = request.send()?; + let json = json::parse(&(response.text()?)).unwrap(); + check_json_success(&json)?; + + Ok(json["logs"] + .members() + .map(|meta| LogMetadata::from_json(meta)) + .collect()) +} + +/// Query logs.tf for logs with the given parameters. Takes a number of retries. +/// Should the first query fail, this is the number of tries it will take until +/// it gives up querying. +/// +/// # Returns +/// The metadata of all logs that fit the search parameters +pub fn search_logs(search_params: SearchParams, num_retries: u8) -> QueryResult> +{ + keep_trying(|| search_logs_once(&search_params), num_retries) +} diff --git a/logs_tf/src/log.rs b/logs_tf/src/log.rs new file mode 100644 index 0000000..9443e75 --- /dev/null +++ b/logs_tf/src/log.rs @@ -0,0 +1,127 @@ +use std::collections::HashMap; +use std::str::FromStr; + +use chrono::{DateTime, Utc}; +use json::JsonValue; +use ucore::SteamID; + +use super::{keep_trying, QueryResult, LOGS_TF_API_BASE}; +use crate::performance::{Performance, Score}; + +pub struct LogMetadata +{ + pub id: u32, + pub date_time: DateTime, + pub map: String, + pub num_players: u8, +} + +pub struct Log +{ + meta: LogMetadata, + performances: HashMap>, + duration_secs: u32, +} + +impl LogMetadata +{ + pub fn from_json(json: &JsonValue) -> Self + { + Self { + id: json["id"].as_u32().unwrap(), + date_time: DateTime::from_timestamp(json["date"].as_i64().unwrap(), 0) + .expect("Failed to parse datetime"), + map: json["map"].as_str().unwrap().to_owned(), + num_players: json["players"].as_u8().unwrap(), + } + } +} + +impl Log +{ + pub(crate) fn new(meta: LogMetadata, duration_secs: u32) -> Self + { + Self { + meta, + performances: HashMap::new(), + duration_secs, + } + } + + fn download_once(id: u32) -> QueryResult + { + let log = reqwest::blocking::get(format!("{}/{}", LOGS_TF_API_BASE, id))? + .text() + .expect("Unable to read response body"); + + let json = json::parse(&log)?; + super::check_json_success(&json)?; + + Ok(Self::from_json(id, &json)) + } + + /// Download the log with the given id from logs.tf and turn it into a + /// format that can be processed by a rating system easily. + pub fn download(id: u32, num_retries: u8) -> QueryResult + { + keep_trying(|| Self::download_once(id), num_retries) + } + + /// Parse the json information as found on logs.tf into a format easily + /// digestible by the rating system. + // XXX: Check presumed logs.tf json for any format deviances + pub fn from_json(id: u32, json: &JsonValue) -> Self + { + let info = &json["info"]; + let duration_secs = info["total_length"] + .as_u32() + .expect("Duration is not an unsigned int"); + let map = info["map"] + .as_str() + .expect("Unable to read map of log") + .to_owned(); + let timestamp = info["date"] + .as_u32() + .expect("Unable to read date as Unix timestamp") as i64; + let date_time = DateTime::from_timestamp(timestamp, 0).expect("Invalid timestamp"); + let num_players = json["names"].members().len() as u8; + + let meta = LogMetadata { + id, + date_time, + map, + num_players, + }; + + let score = Score::from_json(json); + + let mut performances = HashMap::new(); + for (player_id, stats) in json["players"].entries() { + let player_id = + SteamID::from_str(player_id).expect("Player id is not a valid steam id"); + + let player_performances = Performance::extract_all_from_json(&score, stats); + performances.insert(player_id, player_performances); + } + + Self { + meta, + performances, + duration_secs, + } + } + + pub fn meta(&self) -> &LogMetadata { &self.meta } + pub fn duration_secs(&self) -> u32 { self.duration_secs } + pub fn performances(&self) -> &HashMap> { &self.performances } + + pub(crate) fn add_performance(&mut self, player: SteamID, performance: Performance) + { + match self.performances.get_mut(&player) { + Some(perfs) => perfs.push(performance), + None => { + self.performances.insert(player, vec![performance]); + }, + } + } +} diff --git a/logs_tf/src/performance/dm_performance.rs b/logs_tf/src/performance/dm_performance.rs new file mode 100644 index 0000000..5442d5b --- /dev/null +++ b/logs_tf/src/performance/dm_performance.rs @@ -0,0 +1,80 @@ +use std::str::FromStr; + +use json::JsonValue; +use ucore::Class; + +use super::Performance; + +pub struct DMPerformance +{ + pub class: Class, + pub kills: u8, + pub assists: u8, + pub deaths: u8, + pub damage: u32, + pub time_played_secs: u32, +} + +impl DMPerformance +{ + pub fn extract_all_from_json(json: &JsonValue) -> Vec + { + json["class_stats"] + .members() + .map(|class_stats| Self { + class: Class::from_str(class_stats["type"].as_str().unwrap()).unwrap(), + kills: class_stats["kills"].as_u8().unwrap(), + assists: class_stats["assists"].as_u8().unwrap(), + deaths: class_stats["deaths"].as_u8().unwrap(), + damage: class_stats["dmg"].as_u32().unwrap(), + time_played_secs: class_stats["total_time"].as_u32().unwrap(), + }) + .collect() + } +} + +impl From for Performance +{ + fn from(value: DMPerformance) -> Self { Self::DM(value) } +} + +#[cfg(test)] +mod tests +{ + use std::fs::File; + use std::io::Read; + + use super::*; + + #[test] + fn extract_all_from_json() + { + let mut json = String::new(); + File::open("test_data/log_3094861.json") + .expect("Unable to open test file") + .read_to_string(&mut json) + .expect("Unable to read file to string"); + let json = json::parse(&json).expect("Unable to parse json"); + + let perfs = DMPerformance::extract_all_from_json(&json["players"]["[U:1:886717065]"]); + + assert_eq!(perfs.len(), 3); + let scout_perf = &perfs[0]; + let engi_perf = &perfs[1]; + let _pyro_perf = &perfs[2]; + + assert_eq!(scout_perf.class, Class::Scout); + assert_eq!(scout_perf.kills, 19); + assert_eq!(scout_perf.assists, 14); + assert_eq!(scout_perf.deaths, 16); + assert_eq!(scout_perf.damage, 6671); + assert_eq!(scout_perf.time_played_secs, 1618); + + assert_eq!(engi_perf.class, Class::Engineer); + assert_eq!(engi_perf.kills, 0); + assert_eq!(engi_perf.assists, 2); + assert_eq!(engi_perf.deaths, 0); + assert_eq!(engi_perf.damage, 293); + assert_eq!(engi_perf.time_played_secs, 99); + } +} diff --git a/logs_tf/src/performance/medic_performance.rs b/logs_tf/src/performance/medic_performance.rs new file mode 100644 index 0000000..47ebdb1 --- /dev/null +++ b/logs_tf/src/performance/medic_performance.rs @@ -0,0 +1,76 @@ +use std::str::FromStr; + +use json::JsonValue; +use ucore::Class; + +use super::Performance; + +pub struct MedicPerformance +{ + pub healing: u32, + pub average_uber_length_secs: f32, + pub num_ubers: u8, + pub num_drops: u8, + pub deaths: u8, + pub time_played_secs: u32, +} + +impl MedicPerformance +{ + pub fn extract_from_json(json: &JsonValue) -> Option + { + let class_stats = json["class_stats"].members().find(|class_stats| { + Class::from_str(class_stats["type"].as_str().unwrap()).unwrap() == Class::Medic + }); + + if !json.has_key("medicstats") || class_stats.is_none() { + return None; + } + let class_stats = class_stats.unwrap(); + + Some(Self { + healing: json["heal"].as_u32().unwrap_or(0), + average_uber_length_secs: json["medicstats"]["avg_uber_length"] + .as_f32() + .unwrap_or(0.0), + num_ubers: json["ubers"].as_u8().unwrap_or(0), + num_drops: json["drops"].as_u8().unwrap_or(0), + deaths: class_stats["deaths"].as_u8().unwrap_or(0), + time_played_secs: class_stats["total_time"].as_u32().unwrap_or(0), + }) + } +} + +impl From for Performance +{ + fn from(value: MedicPerformance) -> Self { Self::Med(value) } +} + +#[cfg(test)] +mod tests +{ + use std::fs::File; + use std::io::Read; + + use super::*; + + #[test] + fn extract_from_json() + { + let mut json = String::new(); + File::open("test_data/log_3094861.json") + .expect("Unable to open test file") + .read_to_string(&mut json) + .expect("Unable to read file to string"); + let json = json::parse(&json).expect("Unable to parse json"); + + let stats = MedicPerformance::extract_from_json(&json["players"]["[U:1:71020853]"]) + .expect("Unable to find medic performance"); + assert_eq!(stats.healing, 22732); + assert_eq!(stats.average_uber_length_secs, 6.875); + assert_eq!(stats.num_ubers, 12); + assert_eq!(stats.num_drops, 0); + assert_eq!(stats.deaths, 10); + assert_eq!(stats.time_played_secs, 1738); + } +} diff --git a/logs_tf/src/performance/mod.rs b/logs_tf/src/performance/mod.rs new file mode 100644 index 0000000..d73a0c6 --- /dev/null +++ b/logs_tf/src/performance/mod.rs @@ -0,0 +1,44 @@ +pub mod dm_performance; +pub mod medic_performance; +pub mod overall_performance; +pub mod score; + +use dm_performance::DMPerformance; +use json::JsonValue; +use medic_performance::MedicPerformance; +use overall_performance::OverallPerformance; + +pub use self::score::Score; + +/// A `Performance` contains what a player has done in the course of a game. It +/// contains either a generic performance, where data is not available on a per +/// class basis and the specific performance with information of that class, +/// being either a DM class or the medic. +pub enum Performance +{ + Overall(OverallPerformance), + DM(DMPerformance), + Med(MedicPerformance), +} + +impl Performance +{ + pub fn extract_all_from_json(score: &Score, json: &JsonValue) -> Vec + { + let overall_performance = OverallPerformance::from_json(score, json); + let dm_performances = DMPerformance::extract_all_from_json(json); + let med_performance = MedicPerformance::extract_from_json(json); + + let mut performances = vec![overall_performance.into()]; + + for dm_perf in dm_performances { + performances.push(dm_perf.into()); + } + + if let Some(med_performance) = med_performance { + performances.push(med_performance.into()); + } + + performances + } +} diff --git a/logs_tf/src/performance/overall_performance.rs b/logs_tf/src/performance/overall_performance.rs new file mode 100644 index 0000000..e5fac0e --- /dev/null +++ b/logs_tf/src/performance/overall_performance.rs @@ -0,0 +1,53 @@ +use std::str::FromStr; + +use json::JsonValue; + +use super::score::{Score, Team}; +use super::Performance; + +#[derive(Clone)] +pub struct OverallPerformance +{ + pub won_rounds: u8, + pub num_rounds: u8, + pub damage: u32, + pub damage_taken: u32, + pub kills: u8, + pub deaths: u8, + pub num_medkits: u16, + pub medkits_hp: u32, +} + +impl OverallPerformance +{ + pub fn from_json(score: &Score, json: &JsonValue) -> Self + { + let team = Team::from_str(json["team"].as_str().unwrap()).unwrap(); + let won_rounds = score.get_score(team); + let lost_rounds = score.get_score(team.other()); + let num_rounds = won_rounds + lost_rounds; + + let damage = json["dmg"].as_u32().unwrap_or(0); + let damage_taken = json["dt"].as_u32().unwrap_or(0); + let kills = json["kills"].as_u8().unwrap_or(0); + let deaths = json["deaths"].as_u8().unwrap_or(0); + let num_medkits = json["medkits"].as_u16().unwrap_or(0); + let medkits_hp = json["medkits_hp"].as_u32().unwrap_or(0); + + Self { + won_rounds, + num_rounds, + damage, + damage_taken, + kills, + deaths, + num_medkits, + medkits_hp, + } + } +} + +impl From for Performance +{ + fn from(value: OverallPerformance) -> Self { Self::Overall(value) } +} diff --git a/logs_tf/src/performance/score.rs b/logs_tf/src/performance/score.rs new file mode 100644 index 0000000..3aa29c2 --- /dev/null +++ b/logs_tf/src/performance/score.rs @@ -0,0 +1,62 @@ +use std::str::FromStr; + +use json::JsonValue; + +pub struct Score +{ + red: u8, + blue: u8, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Team +{ + Red, + Blue, +} + +impl Score +{ + pub fn new(red: u8, blue: u8) -> Self { Self { red, blue } } + + pub fn from_json(json: &JsonValue) -> Self + { + let red = json["teams"]["Red"]["score"].as_u8().unwrap(); + let blue = json["teams"]["Blue"]["score"].as_u8().unwrap(); + + Self { red, blue } + } + + pub fn get_score(&self, team: Team) -> u8 + { + match team { + Team::Red => self.red, + Team::Blue => self.blue, + } + } +} + +impl Team +{ + pub fn other(self) -> Self + { + match self { + Self::Red => Self::Blue, + Self::Blue => Self::Red, + } + } +} + +impl FromStr for Team +{ + type Err = (); + + fn from_str(s: &str) -> Result + { + match s.trim().to_lowercase().as_str() { + "red" => Ok(Self::Red), + "blue" => Ok(Self::Blue), + _ => Err(()), + } + } +} diff --git a/logs_tf/src/query_error.rs b/logs_tf/src/query_error.rs new file mode 100644 index 0000000..c201718 --- /dev/null +++ b/logs_tf/src/query_error.rs @@ -0,0 +1,56 @@ +use std::error::Error; +use std::fmt; + +use json::JsonError; +use reqwest::Error as HttpError; + +/// Any error that may occur when querying data from logs.tf +#[derive(Debug)] +pub enum QueryError +{ + /// An error that can occur when the connection to logs.tf is unstable or + /// the service is down. + HttpResponse(HttpError), + /// If for whatever reason an invalid Json file is returned by logs.tf or it + /// is corrupted. + JsonParseError(JsonError), + /// The Json object returned always contains `"success": true` or + /// `"success": false` to let the other party know if the query succeeded. + /// If it is false, this error is returned. + Unsuccessful(String), +} + +pub type QueryResult = Result; + +impl From for QueryError +{ + fn from(e: HttpError) -> Self { Self::HttpResponse(e) } +} +impl From for QueryError +{ + fn from(e: JsonError) -> Self { Self::JsonParseError(e) } +} + +impl fmt::Display for QueryError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + Self::HttpResponse(http_e) => { + write!(f, "An error occured contacting logs.tf: {}", http_e) + }, + Self::JsonParseError(json_e) => { + write!(f, "logs.tf did not return valid json: {}", json_e) + }, + Self::Unsuccessful(e) => { + write!( + f, + "logs.tf could not successfully complete the query: {}", + e + ) + }, + } + } +} + +impl Error for QueryError {} diff --git a/logs_tf/src/search_params.rs b/logs_tf/src/search_params.rs new file mode 100644 index 0000000..6467091 --- /dev/null +++ b/logs_tf/src/search_params.rs @@ -0,0 +1,79 @@ +use std::cmp; + +use reqwest::blocking::RequestBuilder; +use ucore::SteamID; + +pub struct SearchParams +{ + pub player_id: Option, + pub title: Option, + pub limit: Option, +} + +impl SearchParams +{ + pub fn player_id(id: SteamID) -> Self + { + Self { + player_id: Some(id), + title: None, + limit: None, + } + } + + pub fn log_title(title: String) -> Self + { + Self { + player_id: None, + title: Some(title), + limit: None, + } + } + + pub fn limit(limit: u16) -> Self + { + let limit = cmp::min(limit, 10000); + + Self { + player_id: None, + title: None, + limit: Some(limit), + } + } + + pub fn add_player_id(mut self, id: SteamID) -> Self + { + self.player_id.replace(id); + self + } + + pub fn add_log_title(mut self, title: String) -> Self + { + self.title.replace(title); + self + } + + pub fn add_limit(mut self, limit: u16) -> Self + { + self.limit.replace(cmp::min(limit, 10000)); + self + } + + pub fn add_params_to_request(&self, request_builder: RequestBuilder) -> RequestBuilder + { + let request_builder = match &self.player_id { + Some(id) => request_builder.query(&[("player", &id.to_id64_string())]), + None => request_builder, + }; + + let request_builder = match &self.title { + Some(name) => request_builder.query(&[("title", &name)]), + None => request_builder, + }; + + match &self.limit { + Some(limit) => request_builder.query(&[("limit", &limit.to_string())]), + None => request_builder, + } + } +}