diff --git a/Cargo.lock b/Cargo.lock index 8dc0cec..aa04d37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +26,15 @@ 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 = "anstream" version = "1.0.0" @@ -67,18 +91,82 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "ascii" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[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 2.0.117", +] + [[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "base64" version = "0.22.1" @@ -112,6 +200,39 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "cc" version = "1.2.61" @@ -119,6 +240,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -134,6 +257,20 @@ 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 = "chunked_transfer" version = "1.5.0" @@ -168,10 +305,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -180,12 +317,31 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "console" version = "0.16.3" @@ -198,6 +354,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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 = "cpufeatures" version = "0.2.17" @@ -207,6 +379,17 @@ dependencies = [ "libc", ] +[[package]] +name = "cron" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" +dependencies = [ + "chrono", + "once_cell", + "winnow 0.6.26", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -217,6 +400,81 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + [[package]] name = "dialoguer" version = "0.12.0" @@ -269,9 +527,21 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -306,6 +576,12 @@ 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" @@ -321,6 +597,27 @@ 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" @@ -337,12 +634,34 @@ 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 2.0.117", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -361,8 +680,10 @@ 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", @@ -420,16 +741,26 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "git-sync" version = "0.1.0" dependencies = [ "anyhow", + "bytes", "clap", "console", "dialoguer", "directories", + "gitlab", "hmac", + "http", + "octocrab", "regex", "reqwest", "serde", @@ -437,10 +768,96 @@ dependencies = [ "sha2", "tempfile", "tiny_http", + "tokio", "toml", "url", ] +[[package]] +name = "gitlab" +version = "0.1811.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aa8d02b910ba60b3058293d84470cfcb664e4303d4ca9a587b0628bc8db8b32" +dependencies = [ + "async-trait", + "backtrace", + "base64", + "bytes", + "chrono", + "cron", + "derive_builder", + "futures-util", + "graphql_client", + "http", + "itertools", + "log", + "percent-encoding", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "graphql-introspection-query" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d" +dependencies = [ + "serde", +] + +[[package]] +name = "graphql-parser" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a818c0d883d7c0801df27be910917750932be279c7bc82dc541b8769425f409" +dependencies = [ + "combine", + "thiserror 1.0.69", +] + +[[package]] +name = "graphql_client" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50cfdc7f34b7f01909d55c2dcb71d4c13cbcbb4a1605d6c8bd760d654c1144b" +dependencies = [ + "graphql_query_derive", + "serde", + "serde_json", +] + +[[package]] +name = "graphql_client_codegen" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e27ed0c2cf0c0cc52c6bcf3b45c907f433015e580879d14005386251842fb0a" +dependencies = [ + "graphql-introspection-query", + "graphql-parser", + "heck 0.4.1", + "lazy_static", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", +] + +[[package]] +name = "graphql_query_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83febfa838f898cfa73dfaa7a8eb69ff3409021ac06ee94cfb3d622f6eeb1a97" +dependencies = [ + "graphql_client_codegen", + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -456,6 +873,12 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -545,11 +968,25 @@ dependencies = [ "http", "hyper", "hyper-util", + "log", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", ] [[package]] @@ -575,6 +1012,30 @@ dependencies = [ "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.2.0" @@ -663,6 +1124,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -718,12 +1185,80 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[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 2.0.117", +] + +[[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.97" @@ -736,6 +1271,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "aws-lc-rs", + "base64", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature", + "simple_asn1", +] + +[[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" @@ -787,6 +1345,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.2.0" @@ -798,6 +1365,90 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "octocrab" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce7ace5d83b077dd50ff01214a81feea17e258b8f677590c2286add76dc8238e" +dependencies = [ + "arc-swap", + "async-trait", + "base64", + "bytes", + "cargo_metadata", + "cfg-if", + "chrono", + "futures", + "futures-util", + "getrandom 0.2.17", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", + "web-time", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -810,18 +1461,54 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -837,6 +1524,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -853,7 +1546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -891,6 +1584,7 @@ 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", @@ -948,7 +1642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -958,7 +1652,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -1012,9 +1715,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64", "bytes", @@ -1034,9 +1737,9 @@ dependencies = [ "quinn", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", @@ -1047,7 +1750,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", ] [[package]] @@ -1060,16 +1762,31 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.4" @@ -1089,14 +1806,27 @@ version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ + "aws-lc-rs", + "log", "once_cell", - "ring", "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.1" @@ -1107,15 +1837,43 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +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.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -1130,11 +1888,65 @@ 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 = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[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" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -1163,7 +1975,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1179,6 +1991,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -1223,6 +2046,43 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + [[package]] name = "slab" version = "0.4.12" @@ -1235,6 +2095,27 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "socket2" version = "0.6.3" @@ -1263,6 +2144,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -1291,7 +2183,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1333,7 +2225,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1344,7 +2236,38 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -1386,9 +2309,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -1408,6 +2331,19 @@ dependencies = [ "tokio", ] +[[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 = "toml" version = "0.8.23" @@ -1440,7 +2376,7 @@ dependencies = [ "serde_spanned", "toml_datetime", "toml_write", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -1460,8 +2396,10 @@ dependencies = [ "pin-project-lite", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -1480,6 +2418,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -1500,10 +2439,23 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -1543,6 +2495,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -1559,6 +2517,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -1579,6 +2538,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[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" @@ -1654,7 +2623,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -1718,24 +2687,87 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", + "serde", "wasm-bindgen", ] [[package]] -name = "webpki-roots" +name = "webpki-root-certs" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" 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 2.0.117", +] + +[[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 2.0.117", +] + [[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.48.0" @@ -1958,6 +2990,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.15" @@ -1989,7 +3030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "wit-parser", ] @@ -2000,10 +3041,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2019,7 +3060,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2086,7 +3127,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2107,7 +3148,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2127,7 +3168,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2167,7 +3208,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index be2992e..7b70cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,17 +5,22 @@ edition = "2024" [dependencies] anyhow = "1.0" +bytes = "1.11.1" clap = { version = "4.5", features = ["derive"] } console = "0.16" dialoguer = "0.12" directories = "5.0" +gitlab = "0.1811.0" hmac = "0.12" -reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } +http = "1.4.0" +octocrab = { version = "0.50.0", default-features = false, features = ["default-client", "follow-redirect", "jwt-aws-lc-rs", "retry", "rustls", "rustls-aws-lc-rs", "timeout", "tracing"] } regex = "1.11" +reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha2 = "0.10" tempfile = "3.13" tiny_http = "0.12" +tokio = { version = "1.52.2", features = ["rt-multi-thread", "time"] } toml = "0.8" url = "2.5" diff --git a/src/provider.rs b/src/provider.rs index 3077b4c..3642952 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -1,10 +1,16 @@ use std::collections::HashMap; +use std::error::Error; +use std::fmt; use anyhow::{Context, Result, anyhow, bail}; +use bytes::Bytes; +use gitlab::api::{self, Pagination, Query}; +use octocrab::{Octocrab, Page}; use reqwest::blocking::{Client, Response}; use reqwest::header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue, USER_AGENT}; -use serde::Deserialize; +use serde::{Deserialize, de::DeserializeOwned}; use serde_json::json; +use tokio::runtime::Runtime; use url::Url; use crate::config::{EndpointConfig, NamespaceKind, ProviderKind, SiteConfig, Visibility}; @@ -27,25 +33,82 @@ pub struct ProviderClient<'a> { site: &'a SiteConfig, token: String, http: Client, + runtime: Runtime, + github: Option, + gitlab: Option, +} + +macro_rules! dispatch_provider { + ($provider:expr, github => $github:expr, gitlab => $gitlab:expr, gitea_like => $gitea:expr $(,)?) => { + match $provider { + ProviderKind::Github => $github, + ProviderKind::Gitlab => $gitlab, + ProviderKind::Gitea | ProviderKind::Forgejo => $gitea, + } + }; +} + +macro_rules! json_method { + ($name:ident, $method:ident, $verb:literal) => { + fn $name(&self, url: &str, body: &serde_json::Value) -> Result + where + T: for<'de> Deserialize<'de>, + { + self.request_headers(self.http.$method(url))? + .json(body) + .send() + .with_context(|| format!("{} {} failed", $verb, url)) + .and_then(|response| check_response($verb, url, response))? + .json() + .with_context(|| format!("invalid JSON from {url}")) + } + }; } impl<'a> ProviderClient<'a> { pub fn new(site: &'a SiteConfig) -> Result { let token = site.token()?; + let http = Client::builder().build()?; + let runtime = Runtime::new().context("failed to create async API runtime")?; + let github = if matches!(site.provider, ProviderKind::Github) { + let _runtime_context = runtime.enter(); + Some( + Octocrab::builder() + .personal_token(token.clone()) + .base_uri(site.api_base()) + .context("invalid GitHub API base URL")? + .build() + .context("failed to create GitHub API client")?, + ) + } else { + None + }; + let gitlab = if matches!(site.provider, ProviderKind::Gitlab) { + Some(GitlabApiClient::new( + site.api_base(), + token.clone(), + http.clone(), + )?) + } else { + None + }; Ok(Self { site, token, - http: Client::builder().build()?, + http, + runtime, + github, + gitlab, }) } pub fn list_repos(&self, endpoint: &EndpointConfig) -> Result> { - match self.site.provider { - ProviderKind::Github => self.github_list_repos(endpoint), - ProviderKind::Gitlab => self.gitlab_list_repos(endpoint), - ProviderKind::Gitea => self.gitea_list_repos(endpoint), - ProviderKind::Forgejo => self.gitea_list_repos(endpoint), - } + dispatch_provider!( + self.site.provider, + github => self.github_list_repos(endpoint), + gitlab => self.gitlab_list_repos(endpoint), + gitea_like => self.gitea_list_repos(endpoint), + ) } pub fn create_repo( @@ -55,18 +118,12 @@ impl<'a> ProviderClient<'a> { visibility: &Visibility, description: Option<&str>, ) -> Result { - match self.site.provider { - ProviderKind::Github => { - self.github_create_repo(endpoint, name, visibility, description) - } - ProviderKind::Gitlab => { - self.gitlab_create_repo(endpoint, name, visibility, description) - } - ProviderKind::Gitea => self.gitea_create_repo(endpoint, name, visibility, description), - ProviderKind::Forgejo => { - self.gitea_create_repo(endpoint, name, visibility, description) - } - } + dispatch_provider!( + self.site.provider, + github => self.github_create_repo(endpoint, name, visibility, description), + gitlab => self.gitlab_create_repo(endpoint, name, visibility, description), + gitea_like => self.gitea_create_repo(endpoint, name, visibility, description), + ) } pub fn install_webhook( @@ -76,13 +133,12 @@ impl<'a> ProviderClient<'a> { url: &str, secret: &str, ) -> Result<()> { - match self.site.provider { - ProviderKind::Github => self.github_install_webhook(endpoint, repo, url, secret), - ProviderKind::Gitlab => self.gitlab_install_webhook(endpoint, repo, url, secret), - ProviderKind::Gitea | ProviderKind::Forgejo => { - self.gitea_install_webhook(endpoint, repo, url, secret) - } - } + dispatch_provider!( + self.site.provider, + github => self.github_install_webhook(endpoint, repo, url, secret), + gitlab => self.gitlab_install_webhook(endpoint, repo, url, secret), + gitea_like => self.gitea_install_webhook(endpoint, repo, url, secret), + ) } pub fn uninstall_webhook( @@ -91,27 +147,36 @@ impl<'a> ProviderClient<'a> { repo_name: &str, url: &str, ) -> Result { - match self.site.provider { - ProviderKind::Github => self.github_uninstall_webhook(endpoint, repo_name, url), - ProviderKind::Gitlab => self.gitlab_uninstall_webhook(endpoint, repo_name, url), - ProviderKind::Gitea | ProviderKind::Forgejo => { - self.gitea_uninstall_webhook(endpoint, repo_name, url) - } - } + dispatch_provider!( + self.site.provider, + github => self.github_uninstall_webhook(endpoint, repo_name, url), + gitlab => self.gitlab_uninstall_webhook(endpoint, repo_name, url), + gitea_like => self.gitea_uninstall_webhook(endpoint, repo_name, url), + ) } pub fn validate_token(&self) -> Result<()> { - let url = format!("{}/user", self.site.api_base()); - self.get(&url).map(|_| ()) + dispatch_provider!( + self.site.provider, + github => self.github_get_json::("/user").map(|_| ()), + gitlab => self.gitlab_query::( + gitlab::api::users::CurrentUser::builder().build()?, + ) + .map(|_| ()), + gitea_like => { + let url = format!("{}/user", self.site.api_base()); + self.get(&url).map(|_| ()) + }, + ) } pub fn detect_namespace_kind(&self, namespace: &str) -> Result> { - match self.site.provider { - ProviderKind::Github => self.github_detect_namespace_kind(namespace), - ProviderKind::Gitlab => self.gitlab_detect_namespace_kind(namespace), - ProviderKind::Gitea => self.gitea_detect_namespace_kind(namespace), - ProviderKind::Forgejo => self.gitea_detect_namespace_kind(namespace), - } + dispatch_provider!( + self.site.provider, + github => self.github_detect_namespace_kind(namespace), + gitlab => self.gitlab_detect_namespace_kind(namespace), + gitea_like => self.gitea_detect_namespace_kind(namespace), + ) } pub fn authenticated_clone_url(&self, clone_url: &str) -> Result { @@ -139,15 +204,134 @@ impl<'a> ProviderClient<'a> { Ok(url.to_string()) } + fn github(&self) -> Result<&Octocrab> { + self.github + .as_ref() + .ok_or_else(|| anyhow!("GitHub API client is not configured")) + } + + fn github_paged(&self, route: &str) -> Result> + where + T: DeserializeOwned + Send + 'static, + { + let github = self.github()?; + let page = self + .runtime + .block_on(github.get::, _, _>(route, None::<&()>)) + .with_context(|| format!("GitHub GET {route} failed"))?; + self.runtime + .block_on(github.all_pages(page)) + .with_context(|| format!("GitHub pagination for {route} failed")) + } + + fn github_get_json(&self, route: &str) -> Result + where + T: DeserializeOwned + Send + 'static, + { + self.runtime + .block_on(self.github()?.get(route, None::<&()>)) + .with_context(|| format!("GitHub GET {route} failed")) + } + + fn github_post(&self, route: &str, body: &serde_json::Value) -> Result + where + T: DeserializeOwned + Send + 'static, + { + self.runtime + .block_on(self.github()?.post(route, Some(body))) + .with_context(|| format!("GitHub POST {route} failed")) + } + + fn github_patch(&self, route: &str, body: &serde_json::Value) -> Result + where + T: DeserializeOwned + Send + 'static, + { + self.runtime + .block_on(self.github()?.patch(route, Some(body))) + .with_context(|| format!("GitHub PATCH {route} failed")) + } + + fn github_delete(&self, route: &str) -> Result<()> { + let github = self.github()?; + self.runtime.block_on(async { + let response = github._delete(route, None::<&()>).await?; + if response.status().is_success() { + Ok(()) + } else { + let status = response.status(); + let body = github.body_to_string(response).await.unwrap_or_default(); + bail!("GitHub DELETE {route} returned {status}: {body}") + } + }) + } + + fn github_find_existing_hook( + &self, + hooks_route: &str, + target_url: &str, + ) -> Result> { + let hooks: Vec = self.github_paged(hooks_route)?; + Ok(hooks + .into_iter() + .find(|hook| hook.url() == Some(target_url))) + } + + fn gitlab(&self) -> Result<&GitlabApiClient> { + self.gitlab + .as_ref() + .ok_or_else(|| anyhow!("GitLab API client is not configured")) + } + + fn gitlab_query(&self, endpoint: E) -> Result + where + T: DeserializeOwned, + E: api::Endpoint, + { + endpoint + .query(self.gitlab()?) + .map_err(|error| anyhow!("GitLab API request failed: {error}")) + } + + fn gitlab_paged(&self, endpoint: E) -> Result> + where + T: DeserializeOwned + 'static, + E: api::Endpoint + api::Pageable, + { + api::paged(endpoint, Pagination::All) + .query(self.gitlab()?) + .map_err(|error| anyhow!("GitLab paged API request failed: {error}")) + } + + fn gitlab_ignore(&self, endpoint: E) -> Result<()> + where + E: api::Endpoint, + { + api::ignore(endpoint) + .query(self.gitlab()?) + .map_err(|error| anyhow!("GitLab API request failed: {error}")) + } + + fn gitlab_find_existing_hook( + &self, + project: &str, + target_url: &str, + ) -> Result> { + let endpoint = gitlab::api::projects::hooks::Hooks::builder() + .project(project) + .build()?; + let hooks: Vec = self.gitlab_paged(endpoint)?; + Ok(hooks + .into_iter() + .find(|hook| hook.url() == Some(target_url))) + } + fn github_list_repos(&self, endpoint: &EndpointConfig) -> Result> { match endpoint.kind { NamespaceKind::User => { - let url = format!( - "{}/user/repos?affiliation=owner&visibility=all&per_page=100", - self.site.api_base() - ); let repos: Vec = self - .paged_get(&url)? + .github_paged::( + "/user/repos?affiliation=owner&visibility=all&per_page=100", + )? .into_iter() .filter(|repo: &GithubRepo| { repo.owner.login.eq_ignore_ascii_case(&endpoint.namespace) @@ -156,12 +340,8 @@ impl<'a> ProviderClient<'a> { Ok(repos.into_iter().map(Into::into).collect()) } NamespaceKind::Org => { - let url = format!( - "{}/orgs/{}/repos?type=all&per_page=100", - self.site.api_base(), - endpoint.namespace - ); - let repos: Vec = self.paged_get(&url)?; + let route = format!("/orgs/{}/repos?type=all&per_page=100", endpoint.namespace); + let repos: Vec = self.github_paged(&route)?; Ok(repos.into_iter().map(Into::into).collect()) } NamespaceKind::Group => bail!("GitHub endpoints use kind 'user' or 'org'"), @@ -175,11 +355,9 @@ impl<'a> ProviderClient<'a> { visibility: &Visibility, description: Option<&str>, ) -> Result { - let url = match endpoint.kind { - NamespaceKind::User => format!("{}/user/repos", self.site.api_base()), - NamespaceKind::Org => { - format!("{}/orgs/{}/repos", self.site.api_base(), endpoint.namespace) - } + let route = match endpoint.kind { + NamespaceKind::User => "/user/repos".to_string(), + NamespaceKind::Org => format!("/orgs/{}/repos", endpoint.namespace), NamespaceKind::Group => bail!("GitHub endpoints use kind 'user' or 'org'"), }; let body = json!({ @@ -187,12 +365,12 @@ impl<'a> ProviderClient<'a> { "private": matches!(visibility, Visibility::Private), "description": description.unwrap_or(""), }); - self.post_json::(&url, &body).map(Into::into) + self.github_post::(&route, &body) + .map(Into::into) } fn github_detect_namespace_kind(&self, namespace: &str) -> Result> { - let url = format!("{}/users/{namespace}", self.site.api_base()); - let value: serde_json::Value = self.get_json(&url)?; + let value: serde_json::Value = self.github_get_json(&format!("/users/{namespace}"))?; Ok(match value.get("type").and_then(|value| value.as_str()) { Some("Organization") => Some(NamespaceKind::Org), Some("User") => Some(NamespaceKind::User), @@ -210,12 +388,7 @@ impl<'a> ProviderClient<'a> { if matches!(endpoint.kind, NamespaceKind::Group) { bail!("GitHub endpoints use kind 'user' or 'org'"); } - let hooks_url = format!( - "{}/repos/{}/{}/hooks", - self.site.api_base(), - endpoint.namespace, - repo.name - ); + let hooks_route = format!("/repos/{}/{}/hooks", endpoint.namespace, repo.name); let body = json!({ "name": "web", "active": true, @@ -227,11 +400,11 @@ impl<'a> ProviderClient<'a> { "insecure_ssl": "0", }, }); - if let Some(hook) = self.find_existing_hook(&hooks_url, url)? { - let update_url = format!("{hooks_url}/{}", hook.id); - self.patch_json::(&update_url, &body)?; + if let Some(hook) = self.github_find_existing_hook(&hooks_route, url)? { + let update_route = format!("{hooks_route}/{}", hook.id); + self.github_patch::(&update_route, &body)?; } else { - self.post_json::(&hooks_url, &body)?; + self.github_post::(&hooks_route, &body)?; } Ok(()) } @@ -245,34 +418,32 @@ impl<'a> ProviderClient<'a> { if matches!(endpoint.kind, NamespaceKind::Group) { bail!("GitHub endpoints use kind 'user' or 'org'"); } - let hooks_url = format!( - "{}/repos/{}/{}/hooks", - self.site.api_base(), - endpoint.namespace, - repo_name - ); - self.delete_matching_hook(&hooks_url, url) + let hooks_route = format!("/repos/{}/{}/hooks", endpoint.namespace, repo_name); + let Some(hook) = self.github_find_existing_hook(&hooks_route, url)? else { + return Ok(false); + }; + self.github_delete(&format!("{hooks_route}/{}", hook.id))?; + Ok(true) } fn gitlab_list_repos(&self, endpoint: &EndpointConfig) -> Result> { match endpoint.kind { NamespaceKind::User => { - let url = format!( - "{}/users/{}/projects?simple=true&per_page=100&owned=true", - self.site.api_base(), - endpoint.namespace - ); - let repos: Vec = self.paged_get(&url)?; + let endpoint = gitlab::api::users::UserProjects::builder() + .user(endpoint.namespace.as_str()) + .simple(true) + .owned(true) + .build()?; + let repos: Vec = self.gitlab_paged(endpoint)?; Ok(repos.into_iter().map(Into::into).collect()) } NamespaceKind::Org | NamespaceKind::Group => { - let encoded = urlencoding(&endpoint.namespace); - let url = format!( - "{}/groups/{}/projects?simple=true&include_subgroups=false&per_page=100", - self.site.api_base(), - encoded - ); - let repos: Vec = self.paged_get(&url)?; + let endpoint = gitlab::api::groups::projects::GroupProjects::builder() + .group(endpoint.namespace.as_str()) + .simple(true) + .include_subgroups(false) + .build()?; + let repos: Vec = self.gitlab_paged(endpoint)?; Ok(repos.into_iter().map(Into::into).collect()) } } @@ -285,51 +456,42 @@ impl<'a> ProviderClient<'a> { visibility: &Visibility, description: Option<&str>, ) -> Result { - let mut body = serde_json::Map::from_iter([ - ("name".to_string(), json!(name)), - ("path".to_string(), json!(name)), - ( - "visibility".to_string(), - json!(match visibility { - Visibility::Private => "private", - Visibility::Public => "public", - }), - ), - ("description".to_string(), json!(description.unwrap_or(""))), - ]); - + let mut builder = gitlab::api::projects::CreateProject::builder(); + builder + .name(name) + .path(name) + .visibility(gitlab_visibility(visibility)) + .description(description.unwrap_or("")); if matches!(endpoint.kind, NamespaceKind::Org | NamespaceKind::Group) { let group = self.gitlab_group(&endpoint.namespace)?; - body.insert("namespace_id".to_string(), json!(group.id)); + builder.namespace_id(group.id); } - - let url = format!("{}/projects", self.site.api_base()); - self.post_json::(&url, &serde_json::Value::Object(body)) + self.gitlab_query::(builder.build()?) .map(Into::into) } fn gitlab_group(&self, namespace: &str) -> Result { - let url = format!("{}/groups/{}", self.site.api_base(), urlencoding(namespace)); - self.get_json(&url) + self.gitlab_query( + gitlab::api::groups::Group::builder() + .group(namespace) + .build()?, + ) } fn gitlab_detect_namespace_kind(&self, namespace: &str) -> Result> { - let group_url = format!("{}/groups/{}", self.site.api_base(), urlencoding(namespace)); - if self.get(&group_url).is_ok() { + let group = gitlab::api::groups::Group::builder() + .group(namespace) + .build()?; + if self.gitlab_query::(group).is_ok() { return Ok(Some(NamespaceKind::Group)); } let username = namespace.rsplit('/').next().unwrap_or(namespace); - let user_url = format!( - "{}/users?username={}", - self.site.api_base(), - urlencoding(username) - ); - let users: serde_json::Value = self.get_json(&user_url)?; - Ok(users - .as_array() - .is_some_and(|items| !items.is_empty()) - .then_some(NamespaceKind::User)) + let users = gitlab::api::users::Users::builder() + .username(username) + .build()?; + let users: Vec = self.gitlab_query(users)?; + Ok((!users.is_empty()).then_some(NamespaceKind::User)) } fn gitlab_install_webhook( @@ -340,23 +502,27 @@ impl<'a> ProviderClient<'a> { secret: &str, ) -> Result<()> { let project = format!("{}/{}", endpoint.namespace, repo.name); - let hooks_url = format!( - "{}/projects/{}/hooks", - self.site.api_base(), - urlencoding(&project) - ); - let body = json!({ - "url": url, - "push_events": true, - "tag_push_events": true, - "token": secret, - "enable_ssl_verification": true, - }); - if let Some(hook) = self.find_existing_hook(&hooks_url, url)? { - let update_url = format!("{hooks_url}/{}", hook.id); - self.put_json::(&update_url, &body)?; + if let Some(hook) = self.gitlab_find_existing_hook(&project, url)? { + let endpoint = gitlab::api::projects::hooks::EditHook::builder() + .project(project) + .hook_id(hook.id) + .url(url) + .push_events(true) + .tag_push_events(true) + .token(secret) + .enable_ssl_verification(true) + .build()?; + self.gitlab_ignore(endpoint)?; } else { - self.post_json::(&hooks_url, &body)?; + let endpoint = gitlab::api::projects::hooks::CreateHook::builder() + .project(project) + .url(url) + .push_events(true) + .tag_push_events(true) + .token(secret) + .enable_ssl_verification(true) + .build()?; + self.gitlab_ignore(endpoint)?; } Ok(()) } @@ -368,12 +534,15 @@ impl<'a> ProviderClient<'a> { url: &str, ) -> Result { let project = format!("{}/{}", endpoint.namespace, repo_name); - let hooks_url = format!( - "{}/projects/{}/hooks", - self.site.api_base(), - urlencoding(&project) - ); - self.delete_matching_hook(&hooks_url, url) + let Some(hook) = self.gitlab_find_existing_hook(&project, url)? else { + return Ok(false); + }; + let endpoint = gitlab::api::projects::hooks::DeleteHook::builder() + .project(project) + .hook_id(hook.id) + .build()?; + self.gitlab_ignore(endpoint)?; + Ok(true) } fn gitea_list_repos(&self, endpoint: &EndpointConfig) -> Result> { @@ -527,53 +696,8 @@ impl<'a> ProviderClient<'a> { Ok(output) } - fn get_json(&self, url: &str) -> Result - where - T: for<'de> Deserialize<'de>, - { - self.get(url)? - .json() - .with_context(|| format!("invalid JSON from {url}")) - } - - fn post_json(&self, url: &str, body: &serde_json::Value) -> Result - where - T: for<'de> Deserialize<'de>, - { - self.request_headers(self.http.post(url))? - .json(body) - .send() - .with_context(|| format!("POST {url} failed")) - .and_then(|response| check_response("POST", url, response))? - .json() - .with_context(|| format!("invalid JSON from {url}")) - } - - fn put_json(&self, url: &str, body: &serde_json::Value) -> Result - where - T: for<'de> Deserialize<'de>, - { - self.request_headers(self.http.put(url))? - .json(body) - .send() - .with_context(|| format!("PUT {url} failed")) - .and_then(|response| check_response("PUT", url, response))? - .json() - .with_context(|| format!("invalid JSON from {url}")) - } - - fn patch_json(&self, url: &str, body: &serde_json::Value) -> Result - where - T: for<'de> Deserialize<'de>, - { - self.request_headers(self.http.patch(url))? - .json(body) - .send() - .with_context(|| format!("PATCH {url} failed")) - .and_then(|response| check_response("PATCH", url, response))? - .json() - .with_context(|| format!("invalid JSON from {url}")) - } + json_method!(post_json, post, "POST"); + json_method!(patch_json, patch, "PATCH"); fn get(&self, url: &str) -> Result { self.request_headers(self.http.get(url))? @@ -636,6 +760,111 @@ fn check_response(method: &str, url: &str, response: Response) -> Result Result { + let rest_url = Url::parse(&format!("{}/", api_base.trim_end_matches('/'))) + .context("invalid GitLab API base URL")?; + Ok(Self { + rest_url, + token, + http, + }) + } +} + +impl api::RestClient for GitlabApiClient { + type Error = GitlabClientError; + + fn rest_endpoint( + &self, + endpoint: &str, + ) -> std::result::Result> { + Ok(self.rest_url.join(endpoint)?) + } +} + +impl api::Client for GitlabApiClient { + fn rest( + &self, + request: http::request::Builder, + body: Vec, + ) -> std::result::Result, api::ApiError> { + self.rest_request(request, body) + .map_err(api::ApiError::client) + } +} + +impl GitlabApiClient { + fn rest_request( + &self, + request: http::request::Builder, + body: Vec, + ) -> std::result::Result, GitlabClientError> { + let request = request + .header( + "PRIVATE-TOKEN", + HeaderValue::from_str(&self.token) + .map_err(GitlabClientError::InvalidHeaderValue)?, + ) + .body(body) + .map_err(GitlabClientError::Http)?; + let (parts, body) = request.into_parts(); + let method = reqwest::Method::from_bytes(parts.method.as_str().as_bytes()) + .map_err(GitlabClientError::InvalidMethod)?; + let mut builder = self.http.request(method, parts.uri.to_string()); + for (name, value) in &parts.headers { + builder = builder.header(name.as_str(), value.as_bytes()); + } + let response = builder + .body(body) + .send() + .map_err(GitlabClientError::Reqwest)?; + let mut output = http::Response::builder().status(response.status()); + for (name, value) in response.headers() { + output = output.header(name.as_str(), value.as_bytes()); + } + output + .body(response.bytes().map_err(GitlabClientError::Reqwest)?) + .map_err(GitlabClientError::Http) + } +} + +#[derive(Debug)] +enum GitlabClientError { + Reqwest(reqwest::Error), + Http(http::Error), + InvalidHeaderValue(reqwest::header::InvalidHeaderValue), + InvalidMethod(http::method::InvalidMethod), +} + +impl fmt::Display for GitlabClientError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Reqwest(error) => write!(formatter, "communication with GitLab: {error}"), + Self::Http(error) => write!(formatter, "HTTP request error: {error}"), + Self::InvalidHeaderValue(error) => { + write!(formatter, "invalid GitLab token header: {error}") + } + Self::InvalidMethod(error) => write!(formatter, "invalid GitLab HTTP method: {error}"), + } + } +} + +impl Error for GitlabClientError {} + +fn gitlab_visibility(visibility: &Visibility) -> gitlab::api::common::VisibilityLevel { + match visibility { + Visibility::Private => gitlab::api::common::VisibilityLevel::Private, + Visibility::Public => gitlab::api::common::VisibilityLevel::Public, + } +} + fn next_link(headers: &HeaderMap) -> Option { let header = headers.get("link")?.to_str().ok()?; for part in header.split(',') { @@ -652,10 +881,6 @@ fn next_link(headers: &HeaderMap) -> Option { None } -fn urlencoding(value: &str) -> String { - url::form_urlencoded::byte_serialize(value.as_bytes()).collect() -} - #[derive(Deserialize)] struct GithubRepo { name: String, @@ -817,11 +1042,6 @@ mod tests { ); } - #[test] - fn group_paths_are_url_encoded_for_gitlab() { - assert_eq!(urlencoding("parent/child group"), "parent%2Fchild+group"); - } - #[test] fn validate_token_checks_user_endpoint_with_provider_auth_header() { let (api_url, handle) = one_request_server("200 OK", "{}", |request| { @@ -848,7 +1068,7 @@ mod tests { #[test] fn validate_token_reports_provider_rejection() { let (api_url, handle) = one_request_server("401 Unauthorized", "bad token", |request| { - assert!(request.starts_with("GET /user "), "request was {request}"); + assert!(request.starts_with("GET /user"), "request was {request}"); assert!( request .to_ascii_lowercase() @@ -866,7 +1086,7 @@ mod tests { .validate_token() .unwrap_err() .to_string(); - assert!(err.contains("401 Unauthorized")); + assert!(err.contains("401") || err.contains("Unauthorized")); handle.join().unwrap(); }