diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..b71f45b --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,65 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + format: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Update Rust + run: rustup update nightly && rustup default nightly + - name: Install rustfmt + run: rustup component add rustfmt + - run: cargo fmt -- --check + + lint: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Update Rust + run: rustup update stable && rustup default stable + - name: Install clippy + run: rustup component add clippy + - run: cargo clippy --all-features -- --deny warnings + + test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Update Rust + run: rustup update stable && rustup default stable + - run: cargo test --all-features + + test-examples: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Update Rust + run: rustup update stable && rustup default stable + - run: cargo test --all-features --examples + + docs: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Update Rust + run: rustup update stable && rustup default stable + - run: cargo doc + + msrv: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Update Rust + run: rustup update stable && rustup default stable + - name: Install cargo-binstall + run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + - name: Install cargo-msrv + run: cargo binstall -y --version 0.16.0-beta.23 cargo-msrv + - name: Verify the MSRV + run: cargo msrv verify --output-format minimal --all-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..b6189a9 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,15 @@ +# Unstable settings +condense_wildcard_suffixes = true +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +group_imports = "StdExternalCrate" +hex_literal_case = "Upper" +imports_granularity = "Item" +newline_style = "Unix" +normalize_comments = true +normalize_doc_attributes = true +reorder_impl_items = true +use_field_init_shorthand = true +wrap_comments = true +version = "Two" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e2bdea5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1894 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" + +[[package]] +name = "async-trait" +version = "0.1.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[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 = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" +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", + "serde", + "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 = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +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.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[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" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "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", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[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.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +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 = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[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.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[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.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[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 = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6" +dependencies = [ + "num-traits", + "rand", + "serde", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.3", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +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 = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "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 = "reqwest-middleware" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror", + "tower-service", +] + +[[package]] +name = "reqwest-retry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a83df1aaec00176d0fabb65dea13f832d2a446ca99107afc17c5d2d4981221d0" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "getrandom", + "http", + "hyper", + "parking_lot 0.11.2", + "reqwest", + "reqwest-middleware", + "retry-policies", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" +dependencies = [ + "rand", +] + +[[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.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +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 = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[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 = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +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 = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "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.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tes" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "ordered-float", + "pretty_assertions", + "reqwest", + "reqwest-middleware", + "reqwest-retry", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot 0.12.3", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[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-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[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.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +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-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.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 = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..941af90 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "tes" +version = "0.1.0" +edition = "2021" +authors = ["The St. Jude Rust Labs developers"] +license = "MIT OR Apache-2.0" +homepage = "https://github.com/stjude-rust-labs/tes" +repository = "https://github.com/stjude-rust-labs/tes" +rust-version = "1.80.0" + +[dependencies] +anyhow = { version = "1.0.87", optional = true } +chrono = { version = "0.4.38", features = ["serde"] } +ordered-float = { version = "4.2.2", features = ["serde"] } +reqwest = { version = "0.12.7", features = ["json"] } +reqwest-middleware = "0.3.3" +reqwest-retry = "0.6.1" +serde = { version = "1.0.209", features = ["derive"] } +serde_json = "1.0.128" +tokio = { version = "1.40.0", features = ["full", "time"] } +tracing = "0.1.40" +url = { version = "2.5.2", features = ["serde"], optional = true } + +[dev-dependencies] +pretty_assertions = "1.4.0" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } + +[features] +default = ["types"] +client = ["dep:anyhow", "types", "dep:url"] +types = ["dep:url"] + +[[example]] +name = "service-info" +required-features = ["client"] + +[[example]] +name = "task-get" +required-features = ["client"] + +[[example]] +name = "task-list-all" +required-features = ["client"] + +[[example]] +name = "task-submit" +required-features = ["client"] + +[lints.rust] +missing_docs = "warn" +nonstandard-style = "warn" +rust-2018-idioms = "warn" +rust-2021-compatibility = "warn" +rust-2024-compatibility = "warn" + +[lints.rustdoc] +broken_intra_doc_links = "warn" + +[lints.clippy] +missing_docs_in_private_items = "warn" + +[package.metadata.docs.rs] +all-features = true diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..0d5b9b2 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2023-Present St. Jude Children's Research Hospital + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..734969a --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-Present St. Jude Children's Research Hospital + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..de94a17 --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +

+

+ tes +

+ +
+ + CI: Status + + + crates.io version + + crates.io downloads + + License: Apache 2.0 + + + License: MIT + +
+ +
+ A crate for working with the Task Execution Service (TES) specification. +
+ +

+ Explore the docs » · + Learn about TES » +

+

+ +## 📚 Getting Started + +The `tes` crate contains types (via the `types` feature) and a simple client +(via the `client` feature) for working with the [Task Execution Service +(TES)][tes-homepage] specification. Briefly, TES is a specification developed to +uniformly submit units of execution ("tasks") to multiple compute environment +through a single HTTP interface. It is of interest mostly when developing +clients or servers that participate in the large-scale submission or monitoring +of jobs. + +To utilize `tes` in your crates, simply add it to your project. + +```bash +# If you want to use the types. +cargo add tes + +# If you also want to use the provided client. +cargo add tes --features client +``` + +After this, you can access the library using the `tes` module in your Rust code. +You can [take a look at the +examples](https://github.com/stjude-rust-labs/tes/tree/main/examples) for +inspiration, but a simple example could look like this. + +```rust +use tes::v1::client; + +#[tokio::main] +async fn main() { + let url = std::env::args().nth(1).expect("url to be present"); + + let client = client::Builder::default() + .url_from_string(url) + .expect("url could not be parsed") + .try_build() + .expect("could not build client"); + + println!( + "{:#?}", + client + .service_info() + .await + .expect("getting service information failed") + ); +} + +``` + +### Minimum Supported Rust Version + +The minimum supported Rust version is currently `1.80.0`. + +There is a CI job that verifies the declared minimum supported version. + +If a contributor submits a PR that uses a feature from a newer version of Rust, +the contributor is responsible for updating the minimum supported version in +the `Cargo.toml`. + +Contributors may update the minimum supported version as-needed to the latest +stable release of Rust. + +To facilitate the discovery of what the minimum supported version should be, +install the `cargo-msrv` tool: + +```bash +cargo install cargo-msrv +``` + +And run the following command: + +```bash +cargo msrv --min 1.80.0 +``` + +If the reported version is newer than the crate's current minimum supported +version, an update is required. + +## 🖥️ Development + +To bootstrap a development environment, please use the following commands. + +```bash +# Clone the repository +git clone git@github.com:stjude-rust-labs/tes.git +cd tes + +# Build the crate in release mode +cargo build --release + +# List out the examples +cargo run --release --example +``` + +## 🚧️ Tests + +Before submitting any pull requests, please make sure the code passes the +following checks (from the root directory). + +```bash +# Run the project's tests. +cargo test --all-features + +# Run the tests for the examples. +cargo test --examples --all-features + +# Ensure the project doesn't have any linting warnings. +cargo clippy --all-features + +# Ensure the project passes `cargo fmt`. +# Currently this requires nightly Rust +cargo +nightly fmt --check + +# Ensure the docs build. +cargo doc +``` + +## 🤝 Contributing + +Contributions, issues and feature requests are welcome! Feel free to check +[issues page](https://github.com/stjude-rust-labs/tes/issues). + +## 📝 License + +This project is licensed as either [Apache 2.0][license-apache] or +[MIT][license-mit] at your discretion. + +Copyright © 2024-Present [St. Jude Children's Research Hospital](https://github.com/stjude). + +[tes-homepage]: https://www.ga4gh.org/product/task-execution-service-tes/ +[license-apache]: https://github.com/stjude-rust-labs/tes/blob/main/LICENSE-APACHE +[license-mit]: https://github.com/stjude-rust-labs/tes/blob/main/LICENSE-MIT diff --git a/docs/FEATURES.md b/docs/FEATURES.md new file mode 100644 index 0000000..5048497 --- /dev/null +++ b/docs/FEATURES.md @@ -0,0 +1,4 @@ +| Feature | Default | Description | +| :----------- | :-----: | :--------------------------------------------------------------- | +| **`client`** | | A simple client that can be used to interact with a TES service. | +| **`types`** | `X` | A representation of all types related to the TES specification. | diff --git a/examples/service-info.rs b/examples/service-info.rs new file mode 100644 index 0000000..9d5ff44 --- /dev/null +++ b/examples/service-info.rs @@ -0,0 +1,39 @@ +//! Gets the descriptive information of the execution service. +//! +//! You can run this with the following command: +//! +//! `TOKEN= RUST_LOG=tes=debug cargo run --release --features=client +//! --example service-info ` + +use anyhow::Context; +use anyhow::Result; +use tes::v1::client; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let url = std::env::args().nth(1).expect("url to be present"); + + let mut builder = client::Builder::default() + .url_from_string(url) + .expect("url could not be parsed"); + + if let Ok(token) = std::env::var("TOKEN") { + builder = builder.insert_header("Authorization", format!("Basic {}", token)); + } + + let client = builder.try_build().expect("could not build client"); + println!( + "{:#?}", + client + .service_info() + .await + .context("getting the service information")? + ); + + Ok(()) +} diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..7c19b19 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,26 @@ +//! A simple example of using the client. +//! +//! You can run this with the following command: +//! +//! `cargo run --release --features=client --example simple ` + +use tes::v1::client; + +#[tokio::main] +async fn main() { + let url = std::env::args().nth(1).expect("url to be present"); + + let client = client::Builder::default() + .url_from_string(url) + .expect("url could not be parsed") + .try_build() + .expect("could not build client"); + + println!( + "{:#?}", + client + .service_info() + .await + .expect("getting service information failed") + ); +} diff --git a/examples/task-get.rs b/examples/task-get.rs new file mode 100644 index 0000000..cd4ebed --- /dev/null +++ b/examples/task-get.rs @@ -0,0 +1,42 @@ +//! Gets a particular task within an execution service. +//! +//! You can run this with the following command: +//! +//! `TOKEN= RUST_LOG=tes=debug cargo run --release --features=client +//! --example task-submit ` + +use anyhow::Context; +use anyhow::Result; +use tes::v1::client; +use tes::v1::client::tasks::View; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let url = std::env::args().nth(1).expect("url to be present"); + let id = std::env::args().nth(2).expect("task id to be present"); + + let mut builder = client::Builder::default() + .url_from_string(url) + .expect("url could not be parsed"); + + if let Ok(token) = std::env::var("TOKEN") { + builder = builder.insert_header("Authorization", format!("Basic {}", token)); + } + + let client = builder.try_build().expect("could not build client"); + + println!( + "{:#?}", + client + .get_task(id, View::Full) + .await + .context("submitting a task")? + ); + + Ok(()) +} diff --git a/examples/task-list-all.rs b/examples/task-list-all.rs new file mode 100644 index 0000000..5ea197a --- /dev/null +++ b/examples/task-list-all.rs @@ -0,0 +1,41 @@ +//! Lists all tasks known about within an execution service. +//! +//! You can run this with the following command: +//! +//! `TOKEN= RUST_LOG=tes=debug cargo run --release --features=client +//! --example task-list-all ` + +use anyhow::Context; +use anyhow::Result; +use tes::v1::client; +use tes::v1::client::tasks::View; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let url = std::env::args().nth(1).expect("url to be present"); + + let mut builder = client::Builder::default() + .url_from_string(url) + .expect("url could not be parsed"); + + if let Ok(token) = std::env::var("TOKEN") { + builder = builder.insert_header("Authorization", format!("Basic {}", token)); + } + + let client = builder.try_build().expect("could not build client"); + + println!( + "{:#?}", + client + .list_all_tasks(View::Full) + .await + .context("listing all tasks")? + ); + + Ok(()) +} diff --git a/examples/task-submit.rs b/examples/task-submit.rs new file mode 100644 index 0000000..94ff9cb --- /dev/null +++ b/examples/task-submit.rs @@ -0,0 +1,63 @@ +//! Submits an example task to an execution service. +//! +//! You can run this with the following command: +//! +//! `TOKEN= RUST_LOG=tes=debug cargo run --release --features=client +//! --example task-submit ` + +use anyhow::Context; +use anyhow::Result; +use tes::v1::client; +use tes::v1::types::task::Executor; +use tes::v1::types::task::Resources; +use tes::v1::types::Task; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let url = std::env::args().nth(1).expect("url to be present"); + + let mut builder = client::Builder::default() + .url_from_string(url) + .expect("url could not be parsed"); + + if let Ok(token) = std::env::var("TOKEN") { + builder = builder.insert_header("Authorization", format!("Basic {}", token)); + } + + let client = builder.try_build().expect("could not build client"); + + let task = Task { + name: Some(String::from("my-task")), + description: Some(String::from("A description.")), + resources: Some(Resources { + cpu_cores: Some(4), + preemptible: Some(true), + ..Default::default() + }), + executors: vec![Executor { + image: String::from("ubuntu:latest"), + command: vec![ + String::from("/bin/bash"), + String::from("-c"), + String::from("echo 'hello, world!'"), + ], + ..Default::default() + }], + ..Default::default() + }; + + println!( + "{:#?}", + client + .create_task(task) + .await + .context("submitting a task")? + ); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..343356b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,19 @@ +//! Facilities for working with the Task Execution Service specification. +//! +//! The Task Execution Service (TES) specification is an effort organized by the +//! Global Alliance for Genomics and Health (GA4GH) to define a common API +//! standard for describing and executing batched execution tasks. You can learn +//! more about the specification at the dedicated [website] or the [Swagger +//! Editor][swagger]. +//! +//! At present, versions 1.x of the specification are supported. +//! +//! ## Features +//! +//! This crate provides the following features. +#![doc = include_str!("../docs/FEATURES.md")] +//! [website]: https://ga4gh.github.io/task-execution-schemas/ +//! [swagger]: +//! https://editor.swagger.io/?url=https://ga4gh.github.io/task-execution-schemas/openapi.yaml + +pub mod v1; diff --git a/src/v1.rs b/src/v1.rs new file mode 100644 index 0000000..cc42fd2 --- /dev/null +++ b/src/v1.rs @@ -0,0 +1,10 @@ +//! Facilities related to v1.x of the specification. + +#[cfg(feature = "client")] +pub mod client; + +#[cfg(feature = "client")] +pub use client::Client; + +#[cfg(feature = "types")] +pub mod types; diff --git a/src/v1/client.rs b/src/v1/client.rs new file mode 100644 index 0000000..4fc3efd --- /dev/null +++ b/src/v1/client.rs @@ -0,0 +1,263 @@ +//! A client for interacting with a Task Execution Service (TES) service. + +use reqwest_middleware::ClientWithMiddleware as ReqwestClient; +use serde::Deserialize; +use serde::Serialize; +use tracing::debug; +use tracing::trace; +use url::Url; + +use crate::v1::client::tasks::View; +use crate::v1::types::responses::task; +use crate::v1::types::responses::task::MinimalTask; +use crate::v1::types::responses::CreateTask; +use crate::v1::types::responses::ListTasks; +use crate::v1::types::responses::ServiceInfo; +use crate::v1::types::Task; + +mod builder; +mod options; +pub mod tasks; + +pub use builder::Builder; +pub use options::Options; + +/// An error within the client. +#[derive(Debug)] +pub enum Error { + /// An error when serializing or deserializing JSON. + SerdeJSON(serde_json::Error), + + /// A middleware error from `reqwest_middleware`. + // Note: `reqwest_middleware` stores these as an `anyhow::Error` internally. + Middlware(anyhow::Error), + + /// An error from `reqwest`. + Reqwest(reqwest::Error), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::SerdeJSON(err) => write!(f, "json serde error: {err}"), + Error::Middlware(err) => write!(f, "middleware error: {err}"), + Error::Reqwest(err) => write!(f, "reqwest error: {err}"), + } + } +} + +impl std::error::Error for Error {} + +/// A [`Result`](std::result::Result) with an [`Error`]. +type Result = std::result::Result; + +impl From for Error { + fn from(value: reqwest_middleware::Error) -> Self { + match value { + reqwest_middleware::Error::Middleware(err) => Error::Middlware(err), + reqwest_middleware::Error::Reqwest(err) => Error::Reqwest(err), + } + } +} + +/// A client for interacting with a service. +#[derive(Debug)] +pub struct Client { + /// The base URL. + url: Url, + + /// The underlying client. + client: ReqwestClient, +} + +impl Client { + /// Gets an empty builder for a [`Client`]. + pub fn builder() -> Builder { + Builder::default() + } + + /// Performs a `GET` request on an endpoint within the service. + /// + /// # Safety + /// + /// Because calls to `get()` are all local to this crate, the provided + /// `endpoint` is assumed to always be joinable to the base URL without + /// issue. + async fn get(&self, endpoint: impl AsRef) -> Result + where + Response: for<'de> Deserialize<'de>, + { + let endpoint = endpoint.as_ref(); + + // SAFETY: as described in the documentation for this method, the URL is + // already validated upon creationg of the [`Client`], and the + // `endpoint` is assumed to always be joinable to that URL, so this + // should always unwrap. + let url = self.url.join(endpoint).unwrap(); + debug!("GET {url}"); + + let bytes = self + .client + .get(url) + .send() + .await + .map_err(Error::from)? + .bytes() + .await + .map_err(Error::Reqwest)?; + + trace!("{bytes:?}"); + + serde_json::from_slice(&bytes).map_err(Error::SerdeJSON) + } + + /// Performs a `POST1` request on an endpoint within the service. + /// + /// # Safety + /// + /// Because calls to `post()` are all local to this crate, the provided + /// `endpoint` is assumed to always be joinable to the base URL without + /// issue. + async fn post(&self, endpoint: impl AsRef, body: Body) -> Result + where + Body: Serialize, + Response: for<'de> Deserialize<'de>, + { + let endpoint = endpoint.as_ref(); + let body = serde_json::to_string(&body).map_err(Error::SerdeJSON)?; + + // SAFETY: as described in the documentation for this method, the URL is + // already validated upon creationg of the [`Client`], and the + // `endpoint` is assumed to always be joinable to that URL, so this + // should always unwrap. + let url = self.url.join(endpoint).unwrap(); + debug!("POST {url} {body}"); + + self.client + .post(url) + .body(body) + .header("Content-Type", "application/json") + .send() + .await + .map_err(Error::from)? + .json::() + .await + .map_err(Error::Reqwest) + } + + /// Gets the service information. + /// + /// This method makes a request to the `GET /service-info` endpoint. + pub async fn service_info(&self) -> Result { + self.get("./service-info").await + } + + /// Lists a single page of tasks within the service. + /// + /// This method makes a request to the `GET /tasks` endpoint. + pub async fn list_tasks( + &self, + view: &View, + next_token: Option<&str>, + ) -> Result> { + let mut url = format!("./tasks?view={view}"); + + if let Some(token) = next_token { + url.push_str("&page_token="); + url.push_str(token); + } + + match view { + View::Minimal => { + let results = self.get::>(url).await?; + + Ok(ListTasks { + next_page_token: results.next_page_token, + tasks: results + .tasks + .into_iter() + .map(task::Response::Minimal) + .collect::>(), + }) + } + View::Basic => { + let results = self.get::>(url).await?; + + Ok(ListTasks { + next_page_token: results.next_page_token, + tasks: results + .tasks + .into_iter() + .map(task::Response::Basic) + .collect::>(), + }) + } + View::Full => { + let results = self.get::>(url).await?; + + Ok(ListTasks { + next_page_token: results.next_page_token, + tasks: results + .tasks + .into_iter() + .map(task::Response::Full) + .collect::>(), + }) + } + } + } + + /// Lists all tasks within the service. + /// + /// This method is a convenience wrapper around [`Self::list_tasks()`] that + /// submits follow on requests and the server says there are more results. + pub async fn list_all_tasks(&self, view: View) -> Result> { + let mut results = Vec::new(); + let mut next_token = None; + let mut page = 1usize; + + loop { + debug!("reading task page {page} with token {next_token:?}",); + + let response = self.list_tasks(&view, next_token.as_deref()).await?; + results.extend(response.tasks); + + next_token = response.next_page_token; + if next_token.is_none() { + break; + } + + page += 1; + } + + Ok(results) + } + + /// Creates a task within the service. + /// + /// This method makes a request to the `POST /tasks` endpoint. + pub async fn create_task(&self, task: Task) -> Result { + self.post("./tasks", task).await + } + + /// Gets a specific task within the service. + /// + /// This method makes a request to the `GET /tasks/{id}` endpoint. + pub async fn get_task(&self, id: impl AsRef, view: View) -> Result { + let url = format!("./tasks/{}?view={view}", id.as_ref()); + + Ok(match view { + View::Minimal => task::Response::Minimal(self.get(url).await?), + View::Basic => task::Response::Basic(self.get(url).await?), + View::Full => task::Response::Full(self.get(url).await?), + }) + } + + /// Cancels a task within the service. + /// + /// This method makes a request to the `POST /tasks/{id}:cancel` endpoint. + pub async fn cancel_task(&self, id: impl AsRef) -> Result<()> { + self.post(format!("./tasks/{}:cancel", id.as_ref()), ()) + .await + } +} diff --git a/src/v1/client/builder.rs b/src/v1/client/builder.rs new file mode 100644 index 0000000..1b85514 --- /dev/null +++ b/src/v1/client/builder.rs @@ -0,0 +1,130 @@ +//! Builders for a [`Client`]. + +use reqwest::header::HeaderValue; +use reqwest::header::IntoHeaderName; +use reqwest_retry::policies::ExponentialBackoff; +use reqwest_retry::RetryTransientMiddleware; +use url::Url; + +use crate::v1::client::Client; +use crate::v1::client::Options; + +/// An error related to a [`Builder`]. +#[derive(Debug)] +pub enum Error { + /// A required field was missing from the builder. + Missing(&'static str), + + /// An error from `reqwest`. + Reqwest(reqwest::Error), + + /// An error related to a URL. + Url(url::ParseError), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Missing(field) => write!(f, "missing required field: {field}"), + Error::Reqwest(err) => write!(f, "reqwest error: {err}"), + Error::Url(err) => write!(f, "url error: {err}"), + } + } +} + +/// A [`Result`](std::result::Result) with an [`Error`]. +pub type Result = std::result::Result; + +/// A builder for a [`Client`](Client). +#[derive(Debug, Default)] +pub struct Builder { + /// The base URL for the requests. + url: Option, + + /// The options passed to the client. + options: Options, +} + +impl Builder { + /// Adds a base URL to the [`Builder`]. + /// + /// # Notes + /// + /// This will silently overwrite any previous base URL declarations provided + /// to the builder. + pub fn url(mut self, url: impl Into) -> Self { + self.url = Some(url.into()); + self + } + + /// Attempts to parse a URL and add it as the base URL within the + /// [`Builder`]. + /// + /// # Notes + /// + /// This will silently overwrite any previous base URL declarations provided + /// to the builder. + pub fn url_from_string(mut self, url: impl AsRef) -> Result { + self.url = Some(url.as_ref().parse::().map_err(Error::Url)?); + Ok(self) + } + + /// Inserts a default header for the client within the [`Builder`]. + /// + /// # Safety + /// + /// This method assumes that you will pass only values with the following + /// conditions (retrieved from the underlying [`HeaderValue`] code that + /// parses this field): + /// + /// Each character in the value: + /// + /// * must be printable ASCII _AND_ not be the delete character, _OR_ + /// * must be the tab character (`\t`). + /// + /// If your `value` does not conform to this expectation, the function will + /// panic. This design decision was chosen to avoid needing to unwrap in + /// the vast majority of cases. + pub fn insert_header(mut self, key: K, value: impl AsRef) -> Self + where + K: IntoHeaderName, + { + let value = value.as_ref(); + self.options.headers.insert::( + key, + HeaderValue::from_str(value) + .unwrap_or_else(|_| panic!("value for header is not allowed: {value}")), + ); + self + } + + /// Sets the maximum retries for the client within the [`Builder`]. + /// + /// # Notes + /// + /// This will silently overwrite any previous maximum retry declarations + /// provided to the builder. + pub fn retries(mut self, value: u32) -> Self { + self.options.retries = value; + self + } + + /// Consumes `self` and attempts to build a [`Client`] from the provided + /// values. + pub fn try_build(self) -> Result { + let url = self.url.map(Ok).unwrap_or(Err(Error::Missing("url")))?; + + let client = reqwest::ClientBuilder::new() + .default_headers(self.options.headers) + .build() + .map_err(Error::Reqwest)?; + + let client = reqwest_middleware::ClientBuilder::new(client) + .with(RetryTransientMiddleware::new_with_policy( + ExponentialBackoff::builder().build_with_max_retries(self.options.retries), + )) + .build(); + + Ok(Client { url, client }) + } +} diff --git a/src/v1/client/options.rs b/src/v1/client/options.rs new file mode 100644 index 0000000..256a0a4 --- /dev/null +++ b/src/v1/client/options.rs @@ -0,0 +1,25 @@ +//! Options for a [`Client`](super::Client). + +use reqwest::header::HeaderMap; + +/// The number of retries to the server by default. +const DEFAULT_RETRIES: u32 = 3; + +/// Options used within a [`Client`](super::Client). +#[derive(Debug)] +pub struct Options { + /// Headers to include in each request. + pub headers: HeaderMap, + + /// The number of retries per request. + pub retries: u32, +} + +impl Default for Options { + fn default() -> Self { + Self { + headers: Default::default(), + retries: DEFAULT_RETRIES, + } + } +} diff --git a/src/v1/client/tasks.rs b/src/v1/client/tasks.rs new file mode 100644 index 0000000..26f5b79 --- /dev/null +++ b/src/v1/client/tasks.rs @@ -0,0 +1,36 @@ +//! Task-related entities used within a client. + +use serde::Deserialize; +use serde::Serialize; + +/// An argument that affects which fields are returned on certain task-related +/// endpoints. + +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum View { + /// Only includes the `id` and `state` fields in the returned task. + #[default] + Minimal, + + /// Includes all available fields except: + /// + /// * Logs for stdout (`tesTask.ExecutorLog.stdout`). + /// * Logs for stderr (`tesTask.ExecutorLog.stderr`). + /// * The content of inputs (`tesInput.content`). + /// * The system logs (`tesTaskLog.system_logs`). + Basic, + + /// Includes all fields. + Full, +} + +impl std::fmt::Display for View { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + View::Minimal => write!(f, "MINIMAL"), + View::Basic => write!(f, "BASIC"), + View::Full => write!(f, "FULL"), + } + } +} diff --git a/src/v1/types.rs b/src/v1/types.rs new file mode 100644 index 0000000..4e73e51 --- /dev/null +++ b/src/v1/types.rs @@ -0,0 +1,6 @@ +//! Types within v1.x of the specification. + +pub mod responses; +pub mod task; + +pub use task::Task; diff --git a/src/v1/types/responses.rs b/src/v1/types/responses.rs new file mode 100644 index 0000000..954b869 --- /dev/null +++ b/src/v1/types/responses.rs @@ -0,0 +1,25 @@ +//! Responses from a service. + +mod service; +pub mod task; + +use serde::Deserialize; +use serde::Serialize; +pub use service::ServiceInfo; + +/// A response from `POST /tasks`. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CreateTask { + /// The ID of the created task. + pub id: String, +} + +/// The response from `GET /tasks`. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct ListTasks { + /// The tasks in this page of results. + pub tasks: Vec, + + /// The token for the next page of results. + pub next_page_token: Option, +} diff --git a/src/v1/types/responses/service.rs b/src/v1/types/responses/service.rs new file mode 100644 index 0000000..89af8ab --- /dev/null +++ b/src/v1/types/responses/service.rs @@ -0,0 +1,194 @@ +//! Responses related to the service itself. + +use chrono::DateTime; +use chrono::Utc; +use serde::Deserialize; +use serde::Serialize; +use url::Url; + +/// Names of specifications supported. +/// +/// Note that, in the case of the Task Execution Service specification, this can +/// only be `"tes"` but it's still technically listed as an enum. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub enum Artifact { + /// A task execution service. + #[serde(rename = "tes")] + #[default] + TaskExecutionService, +} + +/// An organization provided a TES service. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Organization { + /// The organization name. + pub name: String, + + /// A URL for the organization. + pub url: Url, +} + +/// A type of service. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct ServiceType { + /// Namespace in reverse domain name format. + pub group: String, + + /// Name of the specification implemented. + pub artifact: Artifact, + + /// The version of the specification being implemented. + pub version: String, +} + +/// A set of service information for the server. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ServiceInfo { + /// A unique identifier for the service. + pub id: String, + + /// Human-readable name of the service. + pub name: String, + + /// The type of the service. + pub r#type: ServiceType, + + /// An optional description of the service. + pub description: Option, + + /// The organization running the service. + pub organization: Organization, + + /// An optional contact URL. + pub contact_url: Option, + + /// An optional documentation URL. + pub documentation_url: Option, + + /// Timestamp when the service was first available. + pub created_at: Option>, + + /// Timestamp when the service was last updated. + pub updated_at: Option>, + + /// An optional string describing the environment that the service is + /// running within. + pub environment: Option, + + /// The version of the service. + pub version: String, + + /// Lists some, but not necessarily all, storage locations supported by the + /// service. + pub storage: Option>, +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn smoke() { + let content = r#"{ + "id": "org.ga4gh.myservice", + "name": "My project", + "type": { + "group": "org.ga4gh", + "artifact": "tes", + "version": "1.0.0" + }, + "description": "This service provides...", + "organization": { + "name": "My organization", + "url": "https://example.com" + }, + "contactUrl": "mailto:support@example.com", + "documentationUrl": "https://docs.myservice.example.com", + "createdAt": "2019-06-04T12:58:19Z", + "updatedAt": "2019-06-04T12:58:19Z", + "environment": "test", + "version": "1.0.0", + "storage": [ + "file:///path/to/local/funnel-storage", + "s3://ohsu-compbio-funnel/storage" + ] +}"#; + + let result: ServiceInfo = serde_json::from_str(content).unwrap(); + + assert_eq!(result.id, "org.ga4gh.myservice"); + assert_eq!(result.name, "My project"); + assert_eq!(result.r#type.group, "org.ga4gh"); + assert_eq!(result.r#type.artifact, Artifact::TaskExecutionService); + assert_eq!(result.r#type.version, "1.0.0"); + assert_eq!(result.description.unwrap(), "This service provides..."); + assert_eq!(result.organization.name, "My organization"); + assert_eq!(result.organization.url.to_string(), "https://example.com/"); + assert_eq!(result.contact_url.unwrap(), "mailto:support@example.com"); + assert_eq!( + result.documentation_url.unwrap().to_string(), + "https://docs.myservice.example.com/" + ); + assert_eq!( + result.created_at.unwrap().to_rfc3339(), + "2019-06-04T12:58:19+00:00" + ); + assert_eq!( + result.updated_at.unwrap().to_rfc3339(), + "2019-06-04T12:58:19+00:00" + ); + assert_eq!(result.environment.unwrap(), "test"); + assert_eq!(result.version, "1.0.0"); + assert_eq!( + result.storage.unwrap(), + vec![ + "file:///path/to/local/funnel-storage", + "s3://ohsu-compbio-funnel/storage" + ] + ); + } + + #[test] + fn full_conversion() { + let now = DateTime::parse_from_rfc3339("2024-09-07T20:27:35.345673Z") + .unwrap() + .into(); + + let info = ServiceInfo { + id: String::from("org.ga4gh.myservice"), + name: String::from("My Server"), + r#type: ServiceType { + group: String::from("org.ga4gh"), + artifact: Artifact::TaskExecutionService, + version: String::from("1.0.0"), + }, + description: Some(String::from("A description")), + organization: Organization { + name: String::from("My Organization"), + url: Url::try_from("https://example.com").unwrap(), + }, + contact_url: Some(String::from("mailto:foo@bar.com")), + documentation_url: Some(Url::try_from("https://docs.myservice.example.com").unwrap()), + created_at: Some(now), + updated_at: Some(now), + environment: Some(String::from("test")), + version: String::from("1.5.0"), + storage: Some(vec![ + String::from("file:///path/to/local/funnel-storage"), + String::from("s3://ohsu-compbio-funnel/storage"), + ]), + }; + + let serialized = serde_json::to_string(&info).unwrap(); + assert_eq!( + serialized, + r#"{"id":"org.ga4gh.myservice","name":"My Server","type":{"group":"org.ga4gh","artifact":"tes","version":"1.0.0"},"description":"A description","organization":{"name":"My Organization","url":"https://example.com/"},"contactUrl":"mailto:foo@bar.com","documentationUrl":"https://docs.myservice.example.com/","createdAt":"2024-09-07T20:27:35.345673Z","updatedAt":"2024-09-07T20:27:35.345673Z","environment":"test","version":"1.5.0","storage":["file:///path/to/local/funnel-storage","s3://ohsu-compbio-funnel/storage"]}"# + ); + + let deserialized: ServiceInfo = serde_json::from_str(&serialized).unwrap(); + assert_eq!(info, deserialized); + } +} diff --git a/src/v1/types/responses/task.rs b/src/v1/types/responses/task.rs new file mode 100644 index 0000000..3f673bc --- /dev/null +++ b/src/v1/types/responses/task.rs @@ -0,0 +1,73 @@ +//! Responses related to tasks. + +use serde::Deserialize; +use serde::Serialize; + +use crate::v1::types::task::State; +use crate::v1::types::Task; + +/// A response for when `?view=MINIMAL` in a task endpoint. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct MinimalTask { + /// The ID. + pub id: String, + + /// The current state. + pub state: Option, +} + +/// A generalized response for getting tasks with the `view` parameter. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(untagged)] +pub enum Response { + /// A response for when `?view=MINIMAL` in a task endpoint. + Minimal(MinimalTask), + + /// A response for when `?view=BASIC` in a task endpoint. + /// + /// **NOTE:** all of the fields that are optional in the specification for + /// this response are also optional on [`Task`], so we can reuse this + /// struct here instead of subsetting it. + Basic(Task), + + /// A response for when `?view=FULL` in a task endpoint. + Full(Task), +} + +impl Response { + /// Retrieves a reference to the inner [`Minimal`] response if the variant + /// is [`Response::Minimal`]. + pub fn as_minimal(&self) -> Option<&MinimalTask> { + match self { + Response::Minimal(task) => Some(task), + _ => None, + } + } + + /// Consumes `self` and returns the inner [`Minimal`] response if the + /// variant is [`Response::Minimal`]. + pub fn into_minimal(self) -> Option { + match self { + Response::Minimal(task) => Some(task), + _ => None, + } + } + + /// Retrieves a reference to the inner [`Task`] response if the variant + /// is [`Response::Basic`] or [`Response::Full`]. + pub fn as_task(&self) -> Option<&Task> { + match self { + Response::Basic(task) | Response::Full(task) => Some(task), + _ => None, + } + } + + /// Consumes `self` and returns the inner [`Task`] response if the variant + /// is [`Response::Basic`] or [`Response::Full`]. + pub fn into_task(self) -> Option { + match self { + Response::Basic(task) | Response::Full(task) => Some(task), + _ => None, + } + } +} diff --git a/src/v1/types/task.rs b/src/v1/types/task.rs new file mode 100644 index 0000000..47909e9 --- /dev/null +++ b/src/v1/types/task.rs @@ -0,0 +1,199 @@ +//! Tasks submitted to a service. + +use std::collections::HashMap; + +use chrono::DateTime; +use chrono::Utc; +use ordered_float::OrderedFloat; +use serde::Deserialize; +use serde::Serialize; + +pub mod executor; +pub mod file; + +pub use executor::Executor; + +/// State of TES task. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub enum State { + /// An unknown state. + #[serde(rename = "UNKNOWN")] + #[default] + Unknown, + + /// A queued task. + #[serde(rename = "QUEUED")] + Queued, + + /// A task that is initializing. + #[serde(rename = "INITIALIZING")] + Initializing, + + /// A task that is running. + #[serde(rename = "RUNNING")] + Running, + + /// A task that is paused. + #[serde(rename = "PAUSED")] + Paused, + + /// A task that has completed. + #[serde(rename = "COMPLETE")] + Complete, + + /// A task that has errored during execution. + #[serde(rename = "EXECUTOR_ERROR")] + ExecutorError, + + /// A task that has encountered a system error. + #[serde(rename = "SYSTEM_ERROR")] + SystemError, + + /// A task that has been cancelled. + #[serde(rename = "CANCELED")] + Canceled, +} + +impl State { + /// Returns whether a task is still executing or not. + pub fn is_executing(&self) -> bool { + matches!( + self, + Self::Unknown | Self::Queued | Self::Initializing | Self::Running | Self::Paused + ) + } +} + +/// An input for a TES task. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Input { + /// An optional name. + pub name: Option, + + /// An optional description. + pub description: Option, + + /// An optional URL. + pub url: Option, + + /// Where the input will be mounted within the container. + pub path: String, + + /// The type. + #[serde(rename = "type")] + pub r#type: file::Type, + + /// The content. + pub content: Option, +} + +/// An output for a TES task. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Output { + /// An optional name. + pub name: Option, + + /// An optional description. + pub description: Option, + + /// The URL where the result will be stored. + pub url: String, + + /// The path to the output within the container. + pub path: String, + + /// The type. + #[serde(rename = "type")] + pub r#type: file::Type, +} + +/// Requested resources for a TES task. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Resources { + /// The number of CPU cores. + pub cpu_cores: Option, + + /// Whether or not the task prefers to be preemptible. + pub preemptible: Option, + + /// The amount of RAM (in gigabytes). + pub ram_gb: Option>, + + /// The amount of disk space (in gigabytes). + pub disk_gb: Option>, + + /// The zones. + pub zones: Option>, +} + +/// An output file log. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct OutputFileLog { + /// The URL. + pub url: String, + + /// The path within the container. + pub path: String, + + /// The size in bytes. + pub size_bytes: String, +} + +/// A task log. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct TaskLog { + /// The executor logs. + pub logs: Vec, + + /// The start time. + pub start_time: Option>, + + /// The end time. + pub end_time: Option>, + + /// The output file logs. + pub outputs: Option>, + + /// The system logs. + pub system_logs: Option>, +} + +/// A task. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Task { + /// The ID. + pub id: Option, + + /// The current state. + pub state: Option, + + /// The user-provided name. + pub name: Option, + + /// The user-provided description. + pub description: Option, + + /// The inputs. + pub inputs: Option>, + + /// The outputs. + pub outputs: Option>, + + /// The requested resources. + pub resources: Option, + + /// The executors. + pub executors: Vec, + + /// The volumes. + pub volumes: Option>, + + /// The tags. + pub tags: Option>, + + /// The logs. + pub logs: Option>, + + /// The time of creation. + pub creation_time: Option>, +} diff --git a/src/v1/types/task/executor.rs b/src/v1/types/task/executor.rs new file mode 100644 index 0000000..794fd9f --- /dev/null +++ b/src/v1/types/task/executor.rs @@ -0,0 +1,56 @@ +//! Executor declared within tasks. + +use std::collections::HashMap; + +use chrono::DateTime; +use chrono::Utc; +use serde::Deserialize; +use serde::Serialize; + +/// An executor. +/// +/// In short, an executor is a single command that is run in a different +/// container image. [`Executor`]s are run sequentially as they are specified in +/// the task. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Executor { + /// The image. + pub image: String, + + /// The command. + pub command: Vec, + + /// The working directory. + pub workdir: Option, + + /// The path from which to pipe the standard input stream. + pub stdin: Option, + + /// The path to pipe the standard output stream to. + pub stdout: Option, + + /// The path to pipe the standard error stream to. + pub stderr: Option, + + /// The environment variables. + pub env: Option>, +} + +/// A log for an [`Executor`]. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Log { + /// The start time. + pub start_time: Option>, + + /// The end time. + pub end_time: Option>, + + /// The value of the standard output stream. + pub stdout: Option, + + /// The value of the standard error stream. + pub stderr: Option, + + /// The exit code. + pub exit_code: Option, +} diff --git a/src/v1/types/task/file.rs b/src/v1/types/task/file.rs new file mode 100644 index 0000000..f4b67f0 --- /dev/null +++ b/src/v1/types/task/file.rs @@ -0,0 +1,17 @@ +//! Files declared within tasks. + +use serde::Deserialize; +use serde::Serialize; + +/// A type of file. +#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub enum Type { + /// A file. + #[serde(rename = "FILE")] + #[default] + File, + + /// A directory. + #[serde(rename = "DIRECTORY")] + Directory, +}