From e19073eb1fa3c1de8a82b1ef2dd23eb272e59915 Mon Sep 17 00:00:00 2001
From: Clay McLeod
Date: Sun, 8 Sep 2024 09:19:49 -0500
Subject: [PATCH] =?UTF-8?q?feat:=20initial=20commit=20=F0=9F=8E=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/CI.yml | 65 +
.gitignore | 1 +
.rustfmt.toml | 15 +
Cargo.lock | 1894 +++++++++++++++++++++++++++++
Cargo.toml | 63 +
LICENSE-APACHE | 201 +++
LICENSE-MIT | 21 +
README.md | 163 +++
docs/FEATURES.md | 4 +
examples/service-info.rs | 39 +
examples/simple.rs | 26 +
examples/task-get.rs | 42 +
examples/task-list-all.rs | 41 +
examples/task-submit.rs | 63 +
src/lib.rs | 19 +
src/v1.rs | 10 +
src/v1/client.rs | 263 ++++
src/v1/client/builder.rs | 130 ++
src/v1/client/options.rs | 25 +
src/v1/client/tasks.rs | 36 +
src/v1/types.rs | 6 +
src/v1/types/responses.rs | 25 +
src/v1/types/responses/service.rs | 194 +++
src/v1/types/responses/task.rs | 73 ++
src/v1/types/task.rs | 199 +++
src/v1/types/task/executor.rs | 56 +
src/v1/types/task/file.rs | 17 +
27 files changed, 3691 insertions(+)
create mode 100644 .github/workflows/CI.yml
create mode 100644 .gitignore
create mode 100644 .rustfmt.toml
create mode 100644 Cargo.lock
create mode 100644 Cargo.toml
create mode 100644 LICENSE-APACHE
create mode 100644 LICENSE-MIT
create mode 100644 README.md
create mode 100644 docs/FEATURES.md
create mode 100644 examples/service-info.rs
create mode 100644 examples/simple.rs
create mode 100644 examples/task-get.rs
create mode 100644 examples/task-list-all.rs
create mode 100644 examples/task-submit.rs
create mode 100644 src/lib.rs
create mode 100644 src/v1.rs
create mode 100644 src/v1/client.rs
create mode 100644 src/v1/client/builder.rs
create mode 100644 src/v1/client/options.rs
create mode 100644 src/v1/client/tasks.rs
create mode 100644 src/v1/types.rs
create mode 100644 src/v1/types/responses.rs
create mode 100644 src/v1/types/responses/service.rs
create mode 100644 src/v1/types/responses/task.rs
create mode 100644 src/v1/types/task.rs
create mode 100644 src/v1/types/task/executor.rs
create mode 100644 src/v1/types/task/file.rs
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..73a40bd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,163 @@
+
+
+ tes
+
+
+
+
+
+ A crate for working with the Task Execution Serivice (TES) interface.
+
+
+
+ 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,
+}