commit 5855251f70cdbab7a127e6faa2d6904f44bd79b0 Author: Joakim Hulthe Date: Sun Apr 5 15:51:10 2026 +0000 Initial commit: immich-sdk v1.137.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fcce44f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2343 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[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.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +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 = "deadpool" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "immich-sdk" +version = "1.137.0" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "thiserror 2.0.18", + "tokio", + "tokio-test", + "url", + "uuid", + "wiremock", +] + +[[package]] +name = "indexmap" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" +dependencies = [ + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[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.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wiremock" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" +dependencies = [ + "assert-json-diff", + "base64", + "deadpool", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "url", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4ccca55 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "immich-sdk" +version = "1.137.0" +edition = "2024" +authors = ["Your Name "] +description = "A modern Rust SDK for the Immich photo and video management server" +license = "MIT OR Apache-2.0" +repository = "https://github.com/yourusername/immich-sdk" +documentation = "https://docs.rs/immich-sdk" +keywords = ["immich", "photos", "api", "sdk", "client"] +categories = ["api-bindings", "multimedia"] +rust-version = "1.85" + +[dependencies] +reqwest = { version = "0.13", default-features = false, features = ["json", "multipart", "rustls", "query"] } +tokio = { version = "1.44", features = ["full"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +serde_repr = "0.1" +thiserror = "2.0" +uuid = { version = "1.22", features = ["serde", "v4"] } +chrono = { version = "0.4.44", features = ["serde"] } +url = "2.5" +bytes = "1.10" +async-trait = "0.1" + +[dev-dependencies] +tokio-test = "0.4" +wiremock = "0.6" + +[lib] +name = "immich_sdk" +path = "src/lib.rs" + +[lints.clippy] +allow_attributes = "warn" +as_ptr_cast_mut = "warn" +as_underscore = "warn" +borrow_as_ptr = "warn" +implicit_clone = "warn" +undocumented_unsafe_blocks = "warn" +unicode_not_nfc = "warn" +unused_async = "deny" +wildcard_dependencies = "deny" + +[lints.rust] +absolute_paths_not_starting_with_crate = "deny" +explicit_outlives_requirements = "warn" +macro_use_extern_crate = "deny" +missing_abi = "deny" +non_ascii_idents = "forbid" +rust_2018_idioms = { level = "deny", priority = -1 } +single_use_lifetimes = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" diff --git a/README.md b/README.md new file mode 100644 index 0000000..829c838 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# immich-sdk + +A modern Rust SDK for the [Immich](https://immich.app/) photo and video management server. + +## Features + +- **Async-first**: Built on `tokio` and `reqwest` for modern async Rust +- **Builder pattern**: Ergonomic API with fluent builders +- **Type-safe**: Strongly typed models +- **Error handling**: Comprehensive error types with `thiserror` +- **Rustls TLS**: Uses rustls instead of OpenSSL + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +immich-sdk = "1.137" +``` + +## Quick Start + +```rust +use immich_sdk::{Client, error::Result}; + +#[tokio::main] +async fn main() -> Result<()> { + // Create a client + let client = Client::from_url("https://immich.example.com")? + .with_api_key("your-api-key"); + + // List albums + let albums = client.albums().list().execute().await?; + println!("Found {} albums", albums.len()); + + // Upload an asset + let asset = client + .assets() + .upload() + .file("/path/to/photo.jpg") + .device_asset_id("phone-123") + .device_id("my-device") + .execute() + .await?; + + println!("Uploaded asset: {:?}", asset.id); + + Ok(()) +} +``` + +## Authentication + +The SDK supports API key authentication: + +```rust +use immich_sdk::Client; + +let client = Client::from_url("https://immich.example.com")? + .with_api_key("your-api-key-here"); +``` + +## API Modules + +### Assets + +Upload, download, and manage photos and videos: + +```rust +// List all assets +let assets = client.assets().list().execute().await?; + +// Get a specific asset +let asset = client.assets().get(asset_id).execute().await?; + +// Upload a new asset +let uploaded = client + .assets() + .upload() + .file("/path/to/photo.jpg") + .device_asset_id("unique-device-id") + .device_id("device-name") + .favorite() + .execute() + .await?; + +// Delete assets +client + .assets() + .delete() + .id(asset_id) + .execute() + .await?; +``` + +### Albums + +Create and manage albums: + +```rust +// Create an album +let album = client + .albums() + .create() + .name("My Vacation") + .execute() + .await?; + +// Add assets to album +client + .albums() + .add_assets(album.id) + .asset_ids(vec![asset_id1, asset_id2]) + .execute() + .await?; + +// List all albums +let albums = client.albums().list().execute().await?; +``` + +### Server Info + +Get server information: + +```rust +// Get server version +let version = client.server().version().await?; +println!("Server version: {}", version); + +// Get server features +let features = client.server().features().await?; +println!("OAuth enabled: {}", features.oauth); + +// Ping the server +let pong = client.server().ping().await?; +println!("Ping: {}", pong); +``` + +## Error Handling + +The SDK uses the `ImmichError` type for all errors: + +```rust +use immich_sdk::error::ImmichError; + +match client.assets().get(asset_id).execute().await { + Ok(asset) => println!("Found: {:?}", asset), + Err(ImmichError::NotFound(_)) => println!("Asset not found"), + Err(ImmichError::Authentication(_)) => println!("Auth failed"), + Err(e) => println!("Error: {}", e), +} +``` + +## Configuration + +Create a client with custom configuration: + +```rust +use immich_sdk::{Client, Config}; +use std::time::Duration; + +let config = Config::new("https://immich.example.com") + .with_api_key("your-api-key") + .with_timeout(Duration::from_secs(60)); + +let client = Client::new(config)?; +``` + +## License + +This project is licensed under the MIT OR Apache-2.0 license. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Acknowledgments + +- [Immich](https://immich.app/) - The amazing photo and video management solution diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs new file mode 100644 index 0000000..09bf155 --- /dev/null +++ b/examples/basic_usage.rs @@ -0,0 +1,32 @@ +//! Basic usage example for immich-sdk + +use immich_sdk::Client; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client + let client = Client::from_url("https://immich.example.com")?.with_api_key("your-api-key"); + + // Get server version + let version = client.server().version().await?; + println!("Server version: {}", version); + + // List albums + let albums = client.albums().list().execute().await?; + println!("Found {} albums", albums.len()); + + // Create a new album + let album = client + .albums() + .create() + .name("My New Album") + .execute() + .await?; + println!("Created album: {} (ID: {})", album.album_name, album.id); + + // List assets + let assets = client.assets().list().execute().await?; + println!("Found {} assets", assets.len()); + + Ok(()) +} diff --git a/examples/upload_photos.rs b/examples/upload_photos.rs new file mode 100644 index 0000000..c7e9f0c --- /dev/null +++ b/examples/upload_photos.rs @@ -0,0 +1,59 @@ +//! Example: Upload photos to Immich + +use immich_sdk::Client; +use std::path::Path; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client + let client = Client::from_url("https://immich.example.com")?.with_api_key("your-api-key"); + + // Path to the photo + let photo_path = Path::new("/path/to/your/photo.jpg"); + + // Upload the photo + println!("Uploading: {}", photo_path.display()); + + let result = client + .assets() + .upload() + .file(photo_path) + .device_asset_id("my-photo-001") + .device_id("my-computer") + .favorite() + .execute() + .await?; + + match result.status { + immich_sdk::models::AssetUploadStatus::Created => { + println!("Successfully uploaded! Asset ID: {:?}", result.id); + } + immich_sdk::models::AssetUploadStatus::Duplicate => { + println!("Photo already exists (duplicate)"); + } + _ => { + println!("Upload status: {:?}", result.status); + } + } + + // Create an album and add the uploaded asset + if let Some(asset_id) = result.id { + let album = client + .albums() + .create() + .name("Uploaded Photos") + .execute() + .await?; + + client + .albums() + .add_assets(album.id) + .asset_ids([asset_id]) + .execute() + .await?; + + println!("Added to album: {}", album.album_name); + } + + Ok(()) +} diff --git a/src/apis/albums.rs b/src/apis/albums.rs new file mode 100644 index 0000000..5bdaa05 --- /dev/null +++ b/src/apis/albums.rs @@ -0,0 +1,287 @@ +//! Albums API - Manage photo albums + +use crate::{ + Client, + error::Result, + models::{AlbumId, AlbumResponse, AssetId, CreateAlbumRequest, UpdateAlbumRequest}, +}; + +/// API for managing albums +#[derive(Debug, Clone)] +pub struct AlbumsApi { + client: Client, +} + +impl AlbumsApi { + /// Create a new albums API instance + pub const fn new(client: Client) -> Self { + Self { client } + } + + /// List all albums + pub fn list(&self) -> ListAlbumsBuilder { + ListAlbumsBuilder::new(self.client.clone()) + } + + /// Get a single album by ID + pub fn get(&self, id: AlbumId) -> GetAlbumBuilder { + GetAlbumBuilder::new(self.client.clone(), id) + } + + /// Create a new album + pub fn create(&self) -> CreateAlbumBuilder { + CreateAlbumBuilder::new(self.client.clone()) + } + + /// Update an album + pub fn update(&self, id: AlbumId) -> UpdateAlbumBuilder { + UpdateAlbumBuilder::new(self.client.clone(), id) + } + + /// Delete an album + pub fn delete(&self, id: AlbumId) -> DeleteAlbumBuilder { + DeleteAlbumBuilder::new(self.client.clone(), id) + } + + /// Add assets to an album + pub fn add_assets(&self, album_id: AlbumId) -> AddAssetsBuilder { + AddAssetsBuilder::new(self.client.clone(), album_id) + } + + /// Remove assets from an album + pub fn remove_assets(&self, album_id: AlbumId) -> RemoveAssetsBuilder { + RemoveAssetsBuilder::new(self.client.clone(), album_id) + } +} + +/// Builder for listing albums +#[derive(Debug)] +pub struct ListAlbumsBuilder { + client: Client, +} + +impl ListAlbumsBuilder { + /// Create a new list builder + const fn new(client: Client) -> Self { + Self { client } + } + + /// Execute the request + pub async fn execute(self) -> Result> { + let req = self.client.get("/albums"); + let response = self.client.execute(req.build()?).await?; + let albums: Vec = response.json().await?; + Ok(albums) + } +} + +/// Builder for getting a single album +#[derive(Debug)] +pub struct GetAlbumBuilder { + client: Client, + id: AlbumId, +} + +impl GetAlbumBuilder { + /// Create a new get builder + const fn new(client: Client, id: AlbumId) -> Self { + Self { client, id } + } + + /// Execute the request + pub async fn execute(self) -> Result { + let path = format!("/albums/{}", self.id); + let req = self.client.get(&path); + let response = self.client.execute(req.build()?).await?; + let album: AlbumResponse = response.json().await?; + Ok(album) + } +} + +/// Builder for creating an album +#[derive(Debug)] +pub struct CreateAlbumBuilder { + client: Client, + name: Option, + asset_ids: Vec, +} + +impl CreateAlbumBuilder { + /// Create a new create builder + const fn new(client: Client) -> Self { + Self { + client, + name: None, + asset_ids: Vec::new(), + } + } + + /// Set the album name + pub fn name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + + /// Add asset IDs to include in the album + pub fn asset_ids(mut self, ids: impl IntoIterator) -> Self { + self.asset_ids.extend(ids); + self + } + + /// Execute the request + pub async fn execute(self) -> Result { + let name = self.name.ok_or_else(|| { + crate::error::ImmichError::Validation("Album name is required".to_string()) + })?; + + let body = CreateAlbumRequest { + album_name: name, + asset_ids: self.asset_ids, + album_users: Vec::new(), + }; + + let req = self.client.post("/albums").json(&body); + let response = self.client.execute(req.build()?).await?; + let album: AlbumResponse = response.json().await?; + Ok(album) + } +} + +/// Builder for updating an album +#[derive(Debug)] +pub struct UpdateAlbumBuilder { + client: Client, + id: AlbumId, + name: Option, + description: Option, +} + +impl UpdateAlbumBuilder { + /// Create a new update builder + const fn new(client: Client, id: AlbumId) -> Self { + Self { + client, + id, + name: None, + description: None, + } + } + + /// Set the album name + pub fn name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + + /// Set the album description + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Execute the request + pub async fn execute(self) -> Result { + let body = UpdateAlbumRequest { + album_name: self.name, + description: self.description, + }; + + let path = format!("/albums/{}", self.id); + let req = self.client.patch(&path).json(&body); + let response = self.client.execute(req.build()?).await?; + let album: AlbumResponse = response.json().await?; + Ok(album) + } +} + +/// Builder for deleting an album +#[derive(Debug)] +pub struct DeleteAlbumBuilder { + client: Client, + id: AlbumId, +} + +impl DeleteAlbumBuilder { + /// Create a new delete builder + const fn new(client: Client, id: AlbumId) -> Self { + Self { client, id } + } + + /// Execute the request + pub async fn execute(self) -> Result<()> { + let path = format!("/albums/{}", self.id); + let req = self.client.delete(&path); + let _response = self.client.execute(req.build()?).await?; + Ok(()) + } +} + +/// Builder for adding assets to an album +#[derive(Debug)] +pub struct AddAssetsBuilder { + client: Client, + album_id: AlbumId, + asset_ids: Vec, +} + +impl AddAssetsBuilder { + /// Create a new add assets builder + const fn new(client: Client, album_id: AlbumId) -> Self { + Self { + client, + album_id, + asset_ids: Vec::new(), + } + } + + /// Add asset IDs to include + pub fn asset_ids(mut self, ids: impl IntoIterator) -> Self { + self.asset_ids.extend(ids); + self + } + + /// Execute the request + pub async fn execute(self) -> Result { + let path = format!("/albums/{}/assets", self.album_id); + let body = serde_json::json!({ "ids": self.asset_ids }); + let req = self.client.put(&path).json(&body); + let response = self.client.execute(req.build()?).await?; + let album: AlbumResponse = response.json().await?; + Ok(album) + } +} + +/// Builder for removing assets from an album +#[derive(Debug)] +pub struct RemoveAssetsBuilder { + client: Client, + album_id: AlbumId, + asset_ids: Vec, +} + +impl RemoveAssetsBuilder { + /// Create a new remove assets builder + const fn new(client: Client, album_id: AlbumId) -> Self { + Self { + client, + album_id, + asset_ids: Vec::new(), + } + } + + /// Add asset IDs to remove + pub fn asset_ids(mut self, ids: impl IntoIterator) -> Self { + self.asset_ids.extend(ids); + self + } + + /// Execute the request + pub async fn execute(self) -> Result { + let path = format!("/albums/{}/assets", self.album_id); + let body = serde_json::json!({ "ids": self.asset_ids }); + let req = self.client.delete(&path).json(&body); + let response = self.client.execute(req.build()?).await?; + let album: AlbumResponse = response.json().await?; + Ok(album) + } +} diff --git a/src/apis/assets.rs b/src/apis/assets.rs new file mode 100644 index 0000000..ff2efad --- /dev/null +++ b/src/apis/assets.rs @@ -0,0 +1,257 @@ +//! Assets API - Manage photos and videos + +use std::path::Path; + +use crate::{ + Client, + error::{ImmichError, Result}, + models::{AssetId, AssetResponse, AssetUploadResponse, DeleteAssetsRequest}, +}; + +/// API for managing assets (photos and videos) +#[derive(Debug, Clone)] +pub struct AssetsApi { + client: Client, +} + +impl AssetsApi { + /// Create a new assets API instance + pub const fn new(client: Client) -> Self { + Self { client } + } + + /// List assets with optional filters + pub fn list(&self) -> ListAssetsBuilder { + ListAssetsBuilder::new(self.client.clone()) + } + + /// Get a single asset by ID + pub fn get(&self, id: AssetId) -> GetAssetBuilder { + GetAssetBuilder::new(self.client.clone(), id) + } + + /// Upload a new asset + pub fn upload(&self) -> UploadAssetBuilder { + UploadAssetBuilder::new(self.client.clone()) + } + + /// Delete assets + pub fn delete(&self) -> DeleteAssetsBuilder { + DeleteAssetsBuilder::new(self.client.clone()) + } +} + +/// Builder for listing assets +#[derive(Debug)] +pub struct ListAssetsBuilder { + client: Client, + album_id: Option, + is_favorite: Option, + is_trashed: Option, +} + +impl ListAssetsBuilder { + /// Create a new list builder + const fn new(client: Client) -> Self { + Self { + client, + album_id: None, + is_favorite: None, + is_trashed: None, + } + } + + /// Filter by album ID + pub fn with_album_id(mut self, album_id: AssetId) -> Self { + self.album_id = Some(album_id); + self + } + + /// Filter by favorite status + pub fn is_favorite(mut self, favorite: bool) -> Self { + self.is_favorite = Some(favorite); + self + } + + /// Filter by trash status + pub fn is_trashed(mut self, trashed: bool) -> Self { + self.is_trashed = Some(trashed); + self + } + + /// Execute the request + pub async fn execute(self) -> Result> { + let mut req = self.client.get("/assets"); + + if let Some(album_id) = self.album_id { + req = req.query(&[("albumId", album_id.to_string())]); + } + + if let Some(is_favorite) = self.is_favorite { + req = req.query(&[("isFavorite", is_favorite.to_string())]); + } + + if let Some(is_trashed) = self.is_trashed { + req = req.query(&[("isTrashed", is_trashed.to_string())]); + } + + let response = self.client.execute(req.build()?).await?; + let assets: Vec = response.json().await?; + + Ok(assets) + } +} + +/// Builder for getting a single asset +#[derive(Debug)] +pub struct GetAssetBuilder { + client: Client, + id: AssetId, +} + +impl GetAssetBuilder { + /// Create a new get builder + const fn new(client: Client, id: AssetId) -> Self { + Self { client, id } + } + + /// Execute the request + pub async fn execute(self) -> Result { + let path = format!("/assets/{}", self.id); + let req = self.client.get(&path); + let response = self.client.execute(req.build()?).await?; + let asset: AssetResponse = response.json().await?; + + Ok(asset) + } +} + +/// Builder for uploading an asset +#[derive(Debug)] +pub struct UploadAssetBuilder { + client: Client, + file_path: Option, + device_asset_id: Option, + device_id: Option, + is_favorite: bool, +} + +impl UploadAssetBuilder { + /// Create a new upload builder + const fn new(client: Client) -> Self { + Self { + client, + file_path: None, + device_asset_id: None, + device_id: None, + is_favorite: false, + } + } + + /// Set the file path to upload + pub fn file(mut self, path: impl AsRef) -> Self { + self.file_path = Some(path.as_ref().to_string_lossy().to_string()); + self + } + + /// Set the device asset ID (required) + pub fn device_asset_id(mut self, id: impl Into) -> Self { + self.device_asset_id = Some(id.into()); + self + } + + /// Set the device ID (required) + pub fn device_id(mut self, id: impl Into) -> Self { + self.device_id = Some(id.into()); + self + } + + /// Mark as favorite + pub fn favorite(mut self) -> Self { + self.is_favorite = true; + self + } + + /// Execute the upload + pub async fn execute(self) -> Result { + let file_path = self + .file_path + .ok_or_else(|| ImmichError::Validation("File path is required".to_string()))?; + + // Read file + let file_content = tokio::fs::read(&file_path).await?; + let file_name = std::path::Path::new(&file_path) + .file_name() + .ok_or_else(|| ImmichError::Validation("Invalid file path".to_string()))? + .to_string_lossy(); + + // Build multipart form + let mut form = reqwest::multipart::Form::new().part( + "assetData", + reqwest::multipart::Part::bytes(file_content).file_name(file_name.to_string()), + ); + + if let Some(device_asset_id) = self.device_asset_id { + form = form.text("deviceAssetId", device_asset_id); + } + + if let Some(device_id) = self.device_id { + form = form.text("deviceId", device_id); + } + + form = form.text("isFavorite", self.is_favorite.to_string()); + + let req = self.client.post("/assets").multipart(form); + let response = self.client.execute(req.build()?).await?; + let result: AssetUploadResponse = response.json().await?; + + Ok(result) + } +} + +/// Builder for deleting assets +#[derive(Debug)] +pub struct DeleteAssetsBuilder { + client: Client, + ids: Vec, +} + +impl DeleteAssetsBuilder { + /// Create a new delete builder + const fn new(client: Client) -> Self { + Self { + client, + ids: Vec::new(), + } + } + + /// Add an asset ID to delete + pub fn id(mut self, id: AssetId) -> Self { + self.ids.push(id); + self + } + + /// Add multiple asset IDs to delete + pub fn ids(mut self, ids: impl IntoIterator) -> Self { + self.ids.extend(ids); + self + } + + /// Execute the deletion + pub async fn execute(self) -> Result<()> { + if self.ids.is_empty() { + return Err(ImmichError::Validation( + "At least one asset ID is required".to_string(), + )); + } + + let body = DeleteAssetsRequest { + ids: self.ids, + force: false, + }; + let req = self.client.delete("/assets").json(&body); + let _response = self.client.execute(req.build()?).await?; + + Ok(()) + } +} diff --git a/src/apis/mod.rs b/src/apis/mod.rs new file mode 100644 index 0000000..fc3c118 --- /dev/null +++ b/src/apis/mod.rs @@ -0,0 +1,12 @@ +//! API modules for interacting with Immich endpoints + +pub mod albums; +pub mod assets; +pub mod server; +pub mod timeline; + +// Re-export main API modules +pub use albums::AlbumsApi; +pub use assets::AssetsApi; +pub use server::ServerApi; +pub use timeline::TimelineApi; diff --git a/src/apis/server.rs b/src/apis/server.rs new file mode 100644 index 0000000..9ff8d06 --- /dev/null +++ b/src/apis/server.rs @@ -0,0 +1,57 @@ +//! Server API - Get server information + +use crate::{ + Client, + error::Result, + models::{ServerAbout, ServerFeatures, ServerVersion}, +}; + +/// API for server information +#[derive(Debug, Clone)] +pub struct ServerApi { + client: Client, +} + +impl ServerApi { + /// Create a new server API instance + pub const fn new(client: Client) -> Self { + Self { client } + } + + /// Get server version + pub async fn version(&self) -> Result { + let req = self.client.get("/server/version"); + let response = self.client.execute(req.build()?).await?; + let version: ServerVersion = response.json().await?; + Ok(version) + } + + /// Get server features + pub async fn features(&self) -> Result { + let req = self.client.get("/server/features"); + let response = self.client.execute(req.build()?).await?; + let features: ServerFeatures = response.json().await?; + Ok(features) + } + + /// Get server about info + pub async fn about(&self) -> Result { + let req = self.client.get("/server/about"); + let response = self.client.execute(req.build()?).await?; + let about: ServerAbout = response.json().await?; + Ok(about) + } + + /// Ping the server + pub async fn ping(&self) -> Result { + let req = self.client.get("/server/ping"); + let response = self.client.execute(req.build()?).await?; + let ping_response: serde_json::Value = response.json().await?; + // The ping endpoint typically returns {"res": "pong"} + let res = ping_response + .get("res") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + Ok(res.to_string()) + } +} diff --git a/src/apis/timeline.rs b/src/apis/timeline.rs new file mode 100644 index 0000000..03f2bbd --- /dev/null +++ b/src/apis/timeline.rs @@ -0,0 +1,395 @@ +//! Timeline API - Get time-bucketed views of assets + +use crate::{ + Client, + error::Result, + models::{AssetId, AssetOrder, AssetVisibility, TimeBucketAssetResponse, TimeBucketResponse}, +}; + +/// API for timeline operations +#[derive(Debug, Clone)] +pub struct TimelineApi { + client: Client, +} + +impl TimelineApi { + /// Create a new timeline API instance + pub const fn new(client: Client) -> Self { + Self { client } + } + + /// List all time buckets + /// + /// Retrieves a list of all minimal time buckets for organizing assets by date. + pub fn buckets(&self) -> ListTimeBucketsBuilder { + ListTimeBucketsBuilder::new(self.client.clone()) + } + + /// Get assets in a specific time bucket + /// + /// Retrieves all asset IDs and metadata for a given time bucket. + pub fn bucket(&self, time_bucket: impl Into) -> GetTimeBucketBuilder { + GetTimeBucketBuilder::new(self.client.clone(), time_bucket.into()) + } +} + +/// Builder for listing time buckets +#[derive(Debug)] +pub struct ListTimeBucketsBuilder { + client: Client, + album_id: Option, + bbox: Option, + is_favorite: Option, + is_trashed: Option, + order: Option, + person_id: Option, + tag_id: Option, + user_id: Option, + visibility: Option, + with_coordinates: Option, + with_partners: Option, + with_stacked: Option, +} + +impl ListTimeBucketsBuilder { + /// Create a new list time buckets builder + const fn new(client: Client) -> Self { + Self { + client, + album_id: None, + bbox: None, + is_favorite: None, + is_trashed: None, + order: None, + person_id: None, + tag_id: None, + user_id: None, + visibility: None, + with_coordinates: None, + with_partners: None, + with_stacked: None, + } + } + + /// Filter by album ID + pub fn album_id(mut self, album_id: AssetId) -> Self { + self.album_id = Some(album_id); + self + } + + /// Filter by bounding box coordinates (west,south,east,north in WGS84) + pub fn bbox(mut self, bbox: impl Into) -> Self { + self.bbox = Some(bbox.into()); + self + } + + /// Filter by favorite status + pub fn is_favorite(mut self, favorite: bool) -> Self { + self.is_favorite = Some(favorite); + self + } + + /// Filter by trash status + pub fn is_trashed(mut self, trashed: bool) -> Self { + self.is_trashed = Some(trashed); + self + } + + /// Set the sort order (ASC for oldest first, DESC for newest first) + pub fn order(mut self, order: AssetOrder) -> Self { + self.order = Some(order); + self + } + + /// Filter by person ID (face recognition) + pub fn person_id(mut self, person_id: AssetId) -> Self { + self.person_id = Some(person_id); + self + } + + /// Filter by tag ID + pub fn tag_id(mut self, tag_id: AssetId) -> Self { + self.tag_id = Some(tag_id); + self + } + + /// Filter by user ID + pub fn user_id(mut self, user_id: AssetId) -> Self { + self.user_id = Some(user_id); + self + } + + /// Filter by visibility status + pub fn visibility(mut self, visibility: AssetVisibility) -> Self { + self.visibility = Some(visibility); + self + } + + /// Include location data in the response + pub fn with_coordinates(mut self) -> Self { + self.with_coordinates = Some(true); + self + } + + /// Include assets shared by partners + pub fn with_partners(mut self) -> Self { + self.with_partners = Some(true); + self + } + + /// Include stacked assets (only primary assets from stacks when true) + pub fn with_stacked(mut self) -> Self { + self.with_stacked = Some(true); + self + } + + /// Execute the request + pub async fn execute(self) -> Result> { + let mut req = self.client.get("/timeline/buckets"); + + if let Some(album_id) = self.album_id { + req = req.query(&[("albumId", album_id.to_string())]); + } + + if let Some(ref bbox) = self.bbox { + req = req.query(&[("bbox", bbox.as_str())]); + } + + if let Some(is_favorite) = self.is_favorite { + req = req.query(&[("isFavorite", is_favorite.to_string())]); + } + + if let Some(is_trashed) = self.is_trashed { + req = req.query(&[("isTrashed", is_trashed.to_string())]); + } + + if let Some(ref order) = self.order { + let order_str = match order { + AssetOrder::Asc => "ASC", + AssetOrder::Desc => "DESC", + }; + req = req.query(&[("order", order_str)]); + } + + if let Some(person_id) = self.person_id { + req = req.query(&[("personId", person_id.to_string())]); + } + + if let Some(tag_id) = self.tag_id { + req = req.query(&[("tagId", tag_id.to_string())]); + } + + if let Some(user_id) = self.user_id { + req = req.query(&[("userId", user_id.to_string())]); + } + + if let Some(ref visibility) = self.visibility { + let visibility_str = match visibility { + AssetVisibility::Timeline => "timeline", + AssetVisibility::Archived => "archive", + AssetVisibility::Hidden => "hidden", + AssetVisibility::Locked => "locked", + }; + req = req.query(&[("visibility", visibility_str)]); + } + + if let Some(true) = self.with_coordinates { + req = req.query(&[("withCoordinates", "true")]); + } + + if let Some(true) = self.with_partners { + req = req.query(&[("withPartners", "true")]); + } + + if let Some(true) = self.with_stacked { + req = req.query(&[("withStacked", "true")]); + } + + let response = self.client.execute(req.build()?).await?; + let buckets: Vec = response.json().await?; + Ok(buckets) + } +} + +/// Builder for getting a specific time bucket +#[derive(Debug)] +pub struct GetTimeBucketBuilder { + client: Client, + time_bucket: String, + album_id: Option, + bbox: Option, + is_favorite: Option, + is_trashed: Option, + order: Option, + person_id: Option, + tag_id: Option, + user_id: Option, + visibility: Option, + with_coordinates: Option, + with_partners: Option, + with_stacked: Option, +} + +impl GetTimeBucketBuilder { + /// Create a new get time bucket builder + fn new(client: Client, time_bucket: String) -> Self { + Self { + client, + time_bucket, + album_id: None, + bbox: None, + is_favorite: None, + is_trashed: None, + order: None, + person_id: None, + tag_id: None, + user_id: None, + visibility: None, + with_coordinates: None, + with_partners: None, + with_stacked: None, + } + } + + /// Filter by album ID + pub fn album_id(mut self, album_id: AssetId) -> Self { + self.album_id = Some(album_id); + self + } + + /// Filter by bounding box coordinates (west,south,east,north in WGS84) + pub fn bbox(mut self, bbox: impl Into) -> Self { + self.bbox = Some(bbox.into()); + self + } + + /// Filter by favorite status + pub fn is_favorite(mut self, favorite: bool) -> Self { + self.is_favorite = Some(favorite); + self + } + + /// Filter by trash status + pub fn is_trashed(mut self, trashed: bool) -> Self { + self.is_trashed = Some(trashed); + self + } + + /// Set the sort order (ASC for oldest first, DESC for newest first) + pub fn order(mut self, order: AssetOrder) -> Self { + self.order = Some(order); + self + } + + /// Filter by person ID (face recognition) + pub fn person_id(mut self, person_id: AssetId) -> Self { + self.person_id = Some(person_id); + self + } + + /// Filter by tag ID + pub fn tag_id(mut self, tag_id: AssetId) -> Self { + self.tag_id = Some(tag_id); + self + } + + /// Filter by user ID + pub fn user_id(mut self, user_id: AssetId) -> Self { + self.user_id = Some(user_id); + self + } + + /// Filter by visibility status + pub fn visibility(mut self, visibility: AssetVisibility) -> Self { + self.visibility = Some(visibility); + self + } + + /// Include location data in the response + pub fn with_coordinates(mut self) -> Self { + self.with_coordinates = Some(true); + self + } + + /// Include assets shared by partners + pub fn with_partners(mut self) -> Self { + self.with_partners = Some(true); + self + } + + /// Include stacked assets (only primary assets from stacks when true) + pub fn with_stacked(mut self) -> Self { + self.with_stacked = Some(true); + self + } + + /// Execute the request + pub async fn execute(self) -> Result { + let mut req = self + .client + .get("/timeline/bucket") + .query(&[("timeBucket", &self.time_bucket)]); + + if let Some(album_id) = self.album_id { + req = req.query(&[("albumId", album_id.to_string())]); + } + + if let Some(ref bbox) = self.bbox { + req = req.query(&[("bbox", bbox.as_str())]); + } + + if let Some(is_favorite) = self.is_favorite { + req = req.query(&[("isFavorite", is_favorite.to_string())]); + } + + if let Some(is_trashed) = self.is_trashed { + req = req.query(&[("isTrashed", is_trashed.to_string())]); + } + + if let Some(ref order) = self.order { + let order_str = match order { + AssetOrder::Asc => "ASC", + AssetOrder::Desc => "DESC", + }; + req = req.query(&[("order", order_str)]); + } + + if let Some(person_id) = self.person_id { + req = req.query(&[("personId", person_id.to_string())]); + } + + if let Some(tag_id) = self.tag_id { + req = req.query(&[("tagId", tag_id.to_string())]); + } + + if let Some(user_id) = self.user_id { + req = req.query(&[("userId", user_id.to_string())]); + } + + if let Some(ref visibility) = self.visibility { + let visibility_str = match visibility { + AssetVisibility::Timeline => "timeline", + AssetVisibility::Archived => "archive", + AssetVisibility::Hidden => "hidden", + AssetVisibility::Locked => "locked", + }; + req = req.query(&[("visibility", visibility_str)]); + } + + if let Some(true) = self.with_coordinates { + req = req.query(&[("withCoordinates", "true")]); + } + + if let Some(true) = self.with_partners { + req = req.query(&[("withPartners", "true")]); + } + + if let Some(true) = self.with_stacked { + req = req.query(&[("withStacked", "true")]); + } + + let response = self.client.execute(req.build()?).await?; + let bucket: TimeBucketAssetResponse = response.json().await?; + Ok(bucket) + } +} diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..90a5e5e --- /dev/null +++ b/src/client.rs @@ -0,0 +1,232 @@ +//! Client for interacting with the Immich API + +use crate::apis::{AlbumsApi, AssetsApi, ServerApi, TimelineApi}; +use crate::error::{ImmichError, Result}; +use std::time::Duration; + +/// Configuration for the Immich client +#[derive(Debug, Clone)] +pub struct Config { + /// Base URL of the Immich server + pub base_url: String, + /// API key for authentication + pub api_key: Option, + /// Request timeout + pub timeout: Duration, + /// User agent string + pub user_agent: String, +} + +impl Default for Config { + fn default() -> Self { + Self { + base_url: String::new(), + api_key: None, + timeout: Duration::from_secs(30), + user_agent: format!("immich-sdk/{} (Rust)", env!("CARGO_PKG_VERSION")), + } + } +} + +impl Config { + /// Create a new config with the given base URL + pub fn new(base_url: impl Into) -> Self { + Self { + base_url: base_url.into(), + ..Default::default() + } + } + + /// Set the API key + pub fn with_api_key(mut self, api_key: impl Into) -> Self { + self.api_key = Some(api_key.into()); + self + } + + /// Set the timeout + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + /// Set a custom user agent + pub fn with_user_agent(mut self, user_agent: impl Into) -> Self { + self.user_agent = user_agent.into(); + self + } +} + +/// Client for making requests to the Immich API +#[derive(Debug, Clone)] +pub struct Client { + config: Config, + http: reqwest::Client, +} + +impl Client { + /// Create a new client with the given configuration + pub fn new(config: Config) -> Result { + // Validate base URL + let base_url = if config.base_url.ends_with('/') { + config.base_url.trim_end_matches('/').to_string() + } else { + config.base_url.clone() + }; + + if base_url.is_empty() { + return Err(ImmichError::Config("Base URL cannot be empty".to_string())); + } + + // Parse to validate URL + let _ = url::Url::parse(&base_url)?; + + let http = reqwest::Client::builder() + .timeout(config.timeout) + .user_agent(&config.user_agent) + .build()?; + + Ok(Self { + config: Config { base_url, ..config }, + http, + }) + } + + /// Create a client from a base URL string + pub fn from_url(base_url: impl Into) -> Result { + Self::new(Config::new(base_url)) + } + + /// Set the API key + pub fn with_api_key(mut self, api_key: impl Into) -> Self { + self.config.api_key = Some(api_key.into()); + self + } + + /// Get the configuration + pub fn config(&self) -> &Config { + &self.config + } + + /// Get the base URL + pub fn base_url(&self) -> &str { + &self.config.base_url + } + + /// Check if the client has an API key configured + pub fn has_api_key(&self) -> bool { + self.config.api_key.is_some() + } + + /// Get the underlying HTTP client + pub fn http(&self) -> &reqwest::Client { + &self.http + } + + /// Create an authenticated request builder + pub fn request(&self, method: reqwest::Method, path: &str) -> reqwest::RequestBuilder { + let url = format!("{}/api{}", self.config.base_url, path); + let mut builder = self.http.request(method, &url); + + // Add API key authentication + if let Some(ref api_key) = self.config.api_key { + builder = builder.header("x-api-key", api_key); + } + + builder + } + + /// Execute a request and handle common error cases + pub async fn execute(&self, request: reqwest::Request) -> Result { + let response = self.http.execute(request).await?; + + if response.status().is_success() { + Ok(response) + } else { + Err(ImmichError::from_response(response).await) + } + } + + /// Make a GET request + pub fn get(&self, path: &str) -> reqwest::RequestBuilder { + self.request(reqwest::Method::GET, path) + } + + /// Make a POST request + pub fn post(&self, path: &str) -> reqwest::RequestBuilder { + self.request(reqwest::Method::POST, path) + } + + /// Make a PUT request + pub fn put(&self, path: &str) -> reqwest::RequestBuilder { + self.request(reqwest::Method::PUT, path) + } + + /// Make a DELETE request + pub fn delete(&self, path: &str) -> reqwest::RequestBuilder { + self.request(reqwest::Method::DELETE, path) + } + + /// Make a PATCH request + pub fn patch(&self, path: &str) -> reqwest::RequestBuilder { + self.request(reqwest::Method::PATCH, path) + } + + /// Access the albums API + pub fn albums(&self) -> AlbumsApi { + AlbumsApi::new(self.clone()) + } + + /// Access the assets API + pub fn assets(&self) -> AssetsApi { + AssetsApi::new(self.clone()) + } + + /// Access the server API + pub fn server(&self) -> ServerApi { + ServerApi::new(self.clone()) + } + + /// Access the timeline API + pub fn timeline(&self) -> TimelineApi { + TimelineApi::new(self.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_config_builder() { + let config = Config::new("https://example.com") + .with_api_key("test-key") + .with_timeout(Duration::from_secs(60)); + + assert_eq!(config.base_url, "https://example.com"); + assert_eq!(config.api_key, Some("test-key".to_string())); + assert_eq!(config.timeout, Duration::from_secs(60)); + } + + #[test] + fn test_client_creation() { + let client = Client::from_url("https://example.com").unwrap(); + assert_eq!(client.base_url(), "https://example.com"); + assert!(!client.has_api_key()); + + let config = Config::new("https://example.com").with_api_key("test-key"); + let client = Client::new(config).unwrap(); + assert!(client.has_api_key()); + } + + #[test] + fn test_url_normalization() { + let client = Client::from_url("https://example.com/").unwrap(); + assert_eq!(client.base_url(), "https://example.com"); + } + + #[test] + fn test_empty_url_error() { + let result = Client::from_url(""); + assert!(result.is_err()); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..9a5f666 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,113 @@ +//! Error types for the Immich SDK + +use thiserror::Error; + +/// Main error type for the Immich SDK +#[derive(Error, Debug)] +pub enum ImmichError { + /// HTTP request failed + #[error("HTTP request failed: {0}")] + Http(#[from] reqwest::Error), + + /// Authentication failed + #[error("Authentication failed: {0}")] + Authentication(String), + + /// Resource not found + #[error("Resource not found: {0}")] + NotFound(String), + + /// Rate limit exceeded + #[error("Rate limit exceeded. Retry after: {0:?}")] + RateLimited(Option), + + /// API returned an error + #[error("API error: {status} - {message}")] + Api { + /// HTTP status code + status: reqwest::StatusCode, + /// Error message from API + message: String, + }, + + /// Serialization/deserialization error + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + + /// URL parsing error + #[error("URL error: {0}")] + Url(#[from] url::ParseError), + + /// Invalid configuration + #[error("Invalid configuration: {0}")] + Config(String), + + /// Validation error + #[error("Validation error: {0}")] + Validation(String), + + /// File I/O error + #[error("File error: {0}")] + File(#[from] std::io::Error), + + /// Unknown error + #[error("Unknown error: {0}")] + Unknown(String), +} + +/// Result type alias for Immich SDK +pub type Result = std::result::Result; + +/// HTTP status code mapping for common errors +impl ImmichError { + /// Create an error from an HTTP response + pub async fn from_response(response: reqwest::Response) -> Self { + let status = response.status(); + let message = response + .text() + .await + .unwrap_or_else(|_| "Unknown error".to_string()); + + match status { + reqwest::StatusCode::UNAUTHORIZED => ImmichError::Authentication(message), + reqwest::StatusCode::NOT_FOUND => ImmichError::NotFound(message), + reqwest::StatusCode::TOO_MANY_REQUESTS => { + // Parse retry-after header if present + let retry_after = None; // Could parse header here + ImmichError::RateLimited(retry_after) + } + _ => ImmichError::Api { status, message }, + } + } + + /// Check if the error is a rate limit error + pub fn is_rate_limited(&self) -> bool { + matches!(self, ImmichError::RateLimited(_)) + } + + /// Check if the error is an authentication error + pub fn is_auth_error(&self) -> bool { + matches!(self, ImmichError::Authentication(_)) + } + + /// Check if the error is a "not found" error + pub fn is_not_found(&self) -> bool { + matches!(self, ImmichError::NotFound(_)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_error_types() { + let auth_err = ImmichError::Authentication("Invalid API key".to_string()); + assert!(auth_err.is_auth_error()); + assert!(!auth_err.is_not_found()); + + let not_found_err = ImmichError::NotFound("Asset not found".to_string()); + assert!(not_found_err.is_not_found()); + assert!(!not_found_err.is_auth_error()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..15fe85f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,58 @@ +//! # Immich SDK +//! +//! A modern Rust SDK for the [Immich](https://immich.app/) photo and video management server. +//! +//! ## Features +//! +//! - **Async-first**: Built on `tokio` and `reqwest` for modern async Rust +//! - **Builder pattern**: Ergonomic API with fluent builders +//! - **Type-safe**: Strongly typed models +//! - **Error handling**: Comprehensive error types with `thiserror` +//! +//! ## Quick Start +//! +//! ```rust,no_run +//! use immich_sdk::Client; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! // Create a client +//! let client = Client::from_url("https://immich.example.com")? +//! .with_api_key("your-api-key"); +//! +//! // List albums +//! let albums = client.albums().list().execute().await?; +//! println!("Found {} albums", albums.len()); +//! +//! Ok(()) +//! } +//! ``` +//! +//! ## Authentication +//! +//! The SDK supports API key authentication: +//! +//! ```rust,ignore +//! use immich_sdk::Client; +//! +//! let client = Client::from_url("https://immich.example.com")? +//! .with_api_key("your-api-key-here"); +//! # Ok::<(), Box>(()) +//! ``` + +#![warn(missing_docs)] + +pub mod apis; +pub mod client; +pub mod error; +pub mod models; + +// Re-export main types +pub use client::{Client, Config}; +pub use error::{ImmichError, Result}; + +// Re-export models +pub use models::*; + +/// Immich API version this SDK targets +pub const IMMICH_API_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..8e30edd --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,353 @@ +//! Data models for the Immich API + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// Asset ID type alias +pub type AssetId = Uuid; + +/// Album ID type alias +pub type AlbumId = Uuid; + +/// User ID type alias +pub type UserId = Uuid; + +/// Asset response from the API +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssetResponse { + /// Asset ID + pub id: AssetId, + /// Device asset ID + pub device_asset_id: String, + /// Device ID + pub device_id: String, + /// Asset type (IMAGE or VIDEO) + #[serde(rename = "type")] + pub asset_type: AssetType, + /// Original file name + pub original_file_name: String, + /// Original mime type + pub original_mime_type: String, + /// File size in bytes + pub exif_info: Option, + /// Whether asset is a favorite + pub is_favorite: bool, + /// Whether asset is archived + pub is_archived: bool, + /// Whether asset is trashed + pub is_trashed: bool, + /// Created at timestamp + pub created_at: DateTime, + /// Updated at timestamp + pub updated_at: DateTime, +} + +/// Asset type enumeration +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "UPPERCASE")] +pub enum AssetType { + /// Image file + Image, + /// Video file + Video, +} + +/// Asset visibility enumeration +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AssetVisibility { + /// Visible in timeline + Timeline, + /// Archived + Archived, + /// Hidden + Hidden, + /// Locked + Locked, +} + +/// EXIF information for an asset +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExifInfo { + /// File size in bytes + pub file_size_in_byte: Option, + /// Image dimensions + pub exif_image_height: Option, + /// Image width + pub exif_image_width: Option, + /// Orientation + pub orientation: Option, + /// Date taken + pub date_time_original: Option>, + /// GPS latitude + pub latitude: Option, + /// GPS longitude + pub longitude: Option, + /// Camera make + pub make: Option, + /// Camera model + pub model: Option, +} + +/// Album response from the API +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbumResponse { + /// Album ID + pub id: AlbumId, + /// Album name + pub album_name: String, + /// Album description + pub description: String, + /// Album cover thumbnail asset ID + pub album_thumbnail_asset_id: Option, + /// Number of assets in album + pub asset_count: i64, + /// Assets in the album + pub assets: Vec, + /// Created at timestamp + pub created_at: DateTime, + /// Updated at timestamp + pub updated_at: DateTime, + /// Owner ID + pub owner_id: UserId, +} + +/// User response from the API +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserResponse { + /// User ID + pub id: UserId, + /// User email + pub email: String, + /// User name + pub name: String, + /// Whether user is admin + pub is_admin: bool, + /// Whether user has OAuth enabled + pub oauth_enabled: bool, + /// Storage usage in bytes + pub storage_usage_in_bytes: i64, + /// Created at timestamp + pub created_at: DateTime, +} + +/// Server version information +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServerVersion { + /// Major version + pub major: i32, + /// Minor version + pub minor: i32, + /// Patch version + pub patch: i32, +} + +impl std::fmt::Display for ServerVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + +/// Server features information +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServerFeatures { + /// Whether OAuth is enabled + pub oauth: bool, + /// Whether OAuth auto launch is enabled + pub oauth_auto_launch: bool, + /// Whether password login is enabled + pub password_login: bool, + /// Whether config file is present + pub config_file: bool, +} + +/// Server about information +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServerAbout { + /// Version information + pub version: ServerVersion, + /// Version string + pub version_url: String, +} + +/// Create album request +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct CreateAlbumRequest { + /// Album name + pub album_name: String, + /// Asset IDs to add to album + pub asset_ids: Vec, + /// User IDs to share with + pub album_users: Vec, +} + +/// Album user creation info +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbumUserCreate { + /// User ID + pub user_id: UserId, + /// Role (viewer or editor) + pub role: AlbumUserRole, +} + +/// Album user role +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AlbumUserRole { + /// Can only view + Viewer, + /// Can edit + Editor, +} + +/// Update album request +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct UpdateAlbumRequest { + /// Album name + pub album_name: Option, + /// Description + pub description: Option, +} + +/// Asset upload response +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssetUploadResponse { + /// Upload status + pub status: AssetUploadStatus, + /// Asset ID if successful + pub id: Option, + /// Duplicate ID if duplicate + pub duplicate: Option, +} + +/// Asset upload status +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AssetUploadStatus { + /// Upload created new asset + Created, + /// Asset already exists + Duplicate, + /// Upload rejected + Rejected, +} + +/// Delete assets request +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeleteAssetsRequest { + /// Asset IDs to delete + pub ids: Vec, + /// Force delete (skip trash) + pub force: bool, +} + +/// API key response +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiKeyResponse { + /// Key ID + pub id: String, + /// Key name + pub name: String, + /// Created at + pub created_at: DateTime, + /// Updated at + pub updated_at: DateTime, +} + +/// Asset order enumeration +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AssetOrder { + /// Oldest first + Asc, + /// Newest first + Desc, +} + +/// Time bucket response +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeBucketResponse { + /// Number of assets in this time bucket + pub count: i64, + /// Time bucket identifier in YYYY-MM-DD format + pub time_bucket: String, +} + +/// Time bucket asset response - contains arrays of asset data +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeBucketAssetResponse { + /// Array of city names extracted from EXIF GPS data + pub city: Vec>, + /// Array of country names extracted from EXIF GPS data + pub country: Vec>, + /// Array of video durations in HH:MM:SS format (null for images) + pub duration: Vec>, + /// Array of file creation timestamps in UTC + pub file_created_at: Vec, + /// Array of asset IDs in the time bucket + pub id: Vec, + /// Array indicating whether each asset is favorited + pub is_favorite: Vec, + /// Array indicating whether each asset is an image (false for videos) + pub is_image: Vec, + /// Array indicating whether each asset is in the trash + pub is_trashed: Vec, + /// Array of latitude coordinates extracted from EXIF GPS data + pub latitude: Vec>, + /// Array of live photo video asset IDs (null for non-live photos) + pub live_photo_video_id: Vec>, + /// Array of UTC offset hours at the time each photo was taken + pub local_offset_hours: Vec, + /// Array of longitude coordinates extracted from EXIF GPS data + pub longitude: Vec>, + /// Array of owner IDs for each asset + pub owner_id: Vec, + /// Array of projection types for 360° content + pub projection_type: Vec>, + /// Array of aspect ratios (width/height) for each asset + pub ratio: Vec, + /// Array of stack information as [stackId, assetCount] tuples + pub stack: Vec>>, + /// Array of BlurHash strings for generating asset previews + pub thumbhash: Vec>, + /// Array of visibility statuses for each asset + pub visibility: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_asset_type_serialization() { + let asset_type = AssetType::Image; + let json = serde_json::to_string(&asset_type).unwrap(); + assert_eq!(json, r#""IMAGE""#); + } + + #[test] + fn test_server_version_display() { + let version = ServerVersion { + major: 1, + minor: 137, + patch: 0, + }; + assert_eq!(version.to_string(), "1.137.0"); + } +}