diff --git a/sdk/rust/.gitignore b/sdk/rust/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/sdk/rust/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/sdk/rust/Cargo.lock b/sdk/rust/Cargo.lock new file mode 100644 index 0000000..4a3f5f6 --- /dev/null +++ b/sdk/rust/Cargo.lock @@ -0,0 +1,1442 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aether-sdk" +version = "0.1.0" +dependencies = [ + "futures", + "prost", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", + "tonic-prost-build", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[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 = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[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.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[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 = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[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 = "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", + "libc", + "wasi", +] + +[[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", + "wasip2", + "wasip3", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +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.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +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.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[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]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[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 = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[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 = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[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 = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +dependencies = [ + "pulldown-cmark", +] + +[[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 = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[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 = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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", +] + +[[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.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[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" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[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 = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +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-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 = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[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-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", +] + +[[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 = "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.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[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-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 = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "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" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[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 = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml new file mode 100644 index 0000000..128aa4e --- /dev/null +++ b/sdk/rust/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "aether-sdk" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" +description = "Rust client SDK for the Aether distributed key-value store" +repository = "https://github.com/aether-io/aether" + +[dependencies] +tonic = { version = "0.14", features = ["tls-ring"] } +tonic-prost = "0.14" +prost = "0.14" +tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1" +futures = "0.3" +thiserror = "2" + +[build-dependencies] +tonic-prost-build = "0.14" diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs new file mode 100644 index 0000000..c39a962 --- /dev/null +++ b/sdk/rust/build.rs @@ -0,0 +1,21 @@ +fn main() -> Result<(), Box> { + tonic_prost_build::configure().compile_protos( + &[ + "../../proto/common.proto", + "../../proto/kv.proto", + "../../proto/watch.proto", + "../../proto/lease.proto", + "../../proto/auth.proto", + "../../proto/lock.proto", + "../../proto/election.proto", + "../../proto/session.proto", + "../../proto/cluster.proto", + "../../proto/maintenance.proto", + "../../proto/barrier.proto", + "../../proto/queue.proto", + "../../proto/shard.proto", + ], + &["../../proto/"], + )?; + Ok(()) +} diff --git a/sdk/rust/examples/basic.rs b/sdk/rust/examples/basic.rs new file mode 100644 index 0000000..344dafa --- /dev/null +++ b/sdk/rust/examples/basic.rs @@ -0,0 +1,58 @@ +use aether_sdk::{Client, Config}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::connect(Config::new(vec!["127.0.0.1:2379".to_string()])).await?; + + // Put a key-value pair + client.kv().put(b"hello", b"world").await?; + println!("put hello=world"); + + // Get the key + let resp = client.kv().get(b"hello").await?; + for kv in &resp.kvs { + println!( + "got {}={}", + String::from_utf8_lossy(&kv.key), + String::from_utf8_lossy(&kv.value) + ); + } + + // Put with lease + let lease = client.lease().grant(30).await?; + client + .kv() + .put_builder(b"ttl-key", b"ttl-value") + .lease(lease.id) + .send() + .await?; + println!("put ttl-key with lease {}", lease.id); + + // Range scan + let resp = client.kv().range(b"hello", b"hello\xff").await?; + println!("range scan found {} keys", resp.kvs.len()); + + // Delete + let resp = client.kv().delete(b"hello").await?; + println!("deleted {} keys", resp.deleted); + + // Watch — create then recv to get the watch_id + let mut watch = client.watch().watch().await?; + watch + .create(aether_sdk::pb::WatchCreateRequest { + key: b"watch-key".to_vec(), + range_end: Vec::new(), + prev_kv: false, + filters: Vec::new(), + }) + .await?; + if let Some(resp) = watch.recv().await? { + println!("watch created, id={}", resp.watch_id); + watch.cancel(resp.watch_id).await?; + } + + // Cleanup + client.lease().revoke(lease.id).await?; + + Ok(()) +} diff --git a/sdk/rust/src/auth.rs b/sdk/rust/src/auth.rs new file mode 100644 index 0000000..a8b0217 --- /dev/null +++ b/sdk/rust/src/auth.rs @@ -0,0 +1,250 @@ +use crate::pb; +use tonic::Request; + +/// Client for authentication and authorization operations. +pub struct AuthClient { + inner: pb::aether_auth_client::AetherAuthClient, +} + +impl AuthClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_auth_client::AetherAuthClient::new(inner), + } + } + + /// Add a new user. + pub async fn user_add( + &self, + name: impl Into, + password: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .user_add(Request::new(pb::UserAddRequest { + name: name.into(), + password: password.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Delete a user by name. + pub async fn user_delete( + &self, + name: impl Into, + ) -> crate::Result { + let resp = self + .inner + .clone() + .user_delete(Request::new(pb::UserDeleteRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// Get user information. + pub async fn user_get(&self, name: impl Into) -> crate::Result { + let resp = self + .inner + .clone() + .user_get(Request::new(pb::UserGetRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// List all users. + pub async fn user_list(&self) -> crate::Result { + let resp = self + .inner + .clone() + .user_list(Request::new(pb::UserListRequest {})) + .await?; + Ok(resp.into_inner()) + } + + /// Change a user's password. + pub async fn user_change_password( + &self, + name: impl Into, + password: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .user_change_password(Request::new(pb::UserChangePasswordRequest { + name: name.into(), + password: password.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Grant a role to a user. + pub async fn user_grant_role( + &self, + user: impl Into, + role: impl Into, + ) -> crate::Result { + let resp = self + .inner + .clone() + .user_grant_role(Request::new(pb::UserGrantRoleRequest { + user: user.into(), + role: role.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Revoke a role from a user. + pub async fn user_revoke_role( + &self, + user: impl Into, + role: impl Into, + ) -> crate::Result { + let resp = self + .inner + .clone() + .user_revoke_role(Request::new(pb::UserRevokeRoleRequest { + user: user.into(), + role: role.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Add a new role. + pub async fn role_add(&self, name: impl Into) -> crate::Result { + let resp = self + .inner + .clone() + .role_add(Request::new(pb::RoleAddRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// Delete a role by name. + pub async fn role_delete( + &self, + name: impl Into, + ) -> crate::Result { + let resp = self + .inner + .clone() + .role_delete(Request::new(pb::RoleDeleteRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// Get role information. + pub async fn role_get(&self, name: impl Into) -> crate::Result { + let resp = self + .inner + .clone() + .role_get(Request::new(pb::RoleGetRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// List all roles. + pub async fn role_list(&self) -> crate::Result { + let resp = self + .inner + .clone() + .role_list(Request::new(pb::RoleListRequest {})) + .await?; + Ok(resp.into_inner()) + } + + /// Grant a permission to a role. + pub async fn role_grant_permission( + &self, + role: impl Into, + perm: pb::Permission, + ) -> crate::Result { + let resp = self + .inner + .clone() + .role_grant_permission(Request::new(pb::RoleGrantPermissionRequest { + role: role.into(), + permission: Some(perm), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Revoke a permission from a role. + pub async fn role_revoke_permission( + &self, + role: impl Into, + perm: pb::Permission, + ) -> crate::Result { + let resp = self + .inner + .clone() + .role_revoke_permission(Request::new(pb::RoleRevokePermissionRequest { + role: role.into(), + permission: Some(perm), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Authenticate a user and obtain a token. + pub async fn authenticate( + &self, + name: impl Into, + password: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .authenticate(Request::new(pb::AuthenticateRequest { + name: name.into(), + password: password.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Enable authentication. Requires setting a root password. + pub async fn enable( + &self, + root_password: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .auth_enable(Request::new(pb::AuthEnableRequest { + root_password: root_password.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Disable authentication. + pub async fn disable(&self) -> crate::Result { + let resp = self + .inner + .clone() + .auth_disable(Request::new(pb::AuthDisableRequest {})) + .await?; + Ok(resp.into_inner()) + } + + /// Get authentication status. + pub async fn status(&self) -> crate::Result { + let resp = self + .inner + .clone() + .auth_status(Request::new(pb::AuthStatusRequest {})) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/barrier.rs b/sdk/rust/src/barrier.rs new file mode 100644 index 0000000..bbeb070 --- /dev/null +++ b/sdk/rust/src/barrier.rs @@ -0,0 +1,62 @@ +use crate::pb; +use tonic::Request; + +/// Client for distributed barrier operations. +pub struct BarrierClient { + inner: pb::aether_barrier_client::AetherBarrierClient, +} + +impl BarrierClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_barrier_client::AetherBarrierClient::new(inner), + } + } + + /// Create and hold a barrier. Other callers will block until released. + pub async fn create( + &self, + name: impl Into>, + lease_id: i64, + ) -> crate::Result { + let resp = self + .inner + .clone() + .create(Request::new(pb::BarrierCreateRequest { + name: name.into(), + lease_id, + })) + .await?; + Ok(resp.into_inner()) + } + + /// Release the barrier, unblocking all waiters. + pub async fn release( + &self, + name: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .release(Request::new(pb::BarrierReleaseRequest { + name: name.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Query whether the barrier is currently held. + pub async fn query(&self, name: impl Into>) -> crate::Result { + let resp = self + .inner + .clone() + .query(Request::new(pb::BarrierQueryRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/cluster.rs b/sdk/rust/src/cluster.rs new file mode 100644 index 0000000..cb7e3b4 --- /dev/null +++ b/sdk/rust/src/cluster.rs @@ -0,0 +1,67 @@ +use crate::pb; +use tonic::Request; + +/// Client for cluster membership operations. +pub struct ClusterClient { + inner: pb::aether_cluster_client::AetherClusterClient, +} + +impl ClusterClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_cluster_client::AetherClusterClient::new(inner), + } + } + + /// List all cluster members. + pub async fn member_list(&self) -> crate::Result { + let resp = self + .inner + .clone() + .member_list(Request::new(pb::MemberListRequest {})) + .await?; + Ok(resp.into_inner()) + } + + /// Add a new member to the cluster. + pub async fn member_add( + &self, + addr: impl Into, + is_learner: bool, + ) -> crate::Result { + let resp = self + .inner + .clone() + .member_add(Request::new(pb::MemberAddRequest { + addr: addr.into(), + is_learner, + })) + .await?; + Ok(resp.into_inner()) + } + + /// Remove a member from the cluster. + pub async fn member_remove(&self, id: u64) -> crate::Result { + let resp = self + .inner + .clone() + .member_remove(Request::new(pb::MemberRemoveRequest { id })) + .await?; + Ok(resp.into_inner()) + } + + /// Promote a learner to a voter. + pub async fn member_promote(&self, id: u64) -> crate::Result { + let resp = self + .inner + .clone() + .member_promote(Request::new(pb::MemberPromoteRequest { id })) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/election.rs b/sdk/rust/src/election.rs new file mode 100644 index 0000000..4ee3dd6 --- /dev/null +++ b/sdk/rust/src/election.rs @@ -0,0 +1,92 @@ +use crate::pb; +use tonic::{Request, Streaming}; + +/// Client for leader election operations. +pub struct ElectionClient { + inner: pb::aether_election_client::AetherElectionClient, +} + +impl ElectionClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_election_client::AetherElectionClient::new(inner), + } + } + + /// Campaign for leadership. Blocks until elected. + pub async fn campaign( + &self, + name: impl Into>, + lease_id: i64, + value: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .campaign(Request::new(pb::CampaignRequest { + name: name.into(), + lease_id, + value: value.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Query the current leader of an election. + pub async fn leader(&self, name: impl Into>) -> crate::Result { + let resp = self + .inner + .clone() + .leader(Request::new(pb::LeaderRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// Voluntarily resign from leadership. + pub async fn resign( + &self, + leader_key: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .resign(Request::new(pb::ResignRequest { + leader_key: leader_key.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Observe leadership changes as a stream. + pub async fn observe(&self, name: impl Into>) -> crate::Result { + let stream = self + .inner + .clone() + .observe(Request::new(pb::ObserveRequest { name: name.into() })) + .await? + .into_inner(); + Ok(ObserveStream { inner: stream }) + } +} + +/// Stream of leadership change notifications. +pub struct ObserveStream { + inner: Streaming, +} + +impl ObserveStream { + /// Receive the next leadership change notification. + pub async fn recv(&mut self) -> crate::Result> { + use futures::StreamExt; + match self.inner.next().await { + Some(Ok(resp)) => Ok(Some(resp)), + Some(Err(e)) => Err(crate::Error::Grpc(e)), + None => Ok(None), + } + } +} diff --git a/sdk/rust/src/keep_alive.rs b/sdk/rust/src/keep_alive.rs new file mode 100644 index 0000000..8358dbc --- /dev/null +++ b/sdk/rust/src/keep_alive.rs @@ -0,0 +1,76 @@ +use futures::StreamExt; +use std::time::Duration; +use tokio::task::JoinHandle; +use tonic::Streaming; + +/// Background keep-alive task that periodically sends requests over a bidi stream. +pub struct KeepAliveHandle { + stop_tx: tokio::sync::watch::Sender, + handle: Option>, +} + +impl KeepAliveHandle { + /// Stop the keep-alive task and wait for it to finish. + pub async fn stop(&mut self) { + let _ = self.stop_tx.send(true); + if let Some(h) = self.handle.take() + && let Err(e) = h.await + { + eprintln!("keep-alive task panicked: {e}"); + } + } +} + +impl Drop for KeepAliveHandle { + fn drop(&mut self) { + let _ = self.stop_tx.send(true); + } +} + +/// Spawn a background keep-alive loop that sends `make_req()` every `interval` +/// and drains `inbound` for acks / errors. +pub(crate) fn spawn( + tx: tokio::sync::mpsc::Sender, + mut inbound: Streaming, + interval: Duration, + make_req: impl Fn() -> Req + Send + 'static, + on_error: Option>, +) -> KeepAliveHandle { + let (stop_tx, mut stop_rx) = tokio::sync::watch::channel(false); + + let handle: JoinHandle<()> = tokio::spawn(async move { + let mut ticker = tokio::time::interval(interval); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + + loop { + tokio::select! { + _ = stop_rx.changed() => break, + _ = ticker.tick() => { + if tx.send(make_req()).await.is_err() { + if let Some(ref cb) = on_error { + cb(crate::Error::Grpc(tonic::Status::internal("keep-alive stream closed"))); + } + break; + } + } + resp = inbound.next() => { + match resp { + Some(Ok(_)) => {} + Some(Err(e)) => { + if let Some(ref cb) = on_error { + cb(crate::Error::Grpc(e)); + } + break; + } + None => break, + } + } + } + } + }); + + KeepAliveHandle { + stop_tx, + handle: Some(handle), + } +} diff --git a/sdk/rust/src/kv.rs b/sdk/rust/src/kv.rs new file mode 100644 index 0000000..314de0c --- /dev/null +++ b/sdk/rust/src/kv.rs @@ -0,0 +1,345 @@ +use crate::pb; +use tonic::Request; + +/// Client for key-value operations. +pub struct KvClient { + inner: pb::aether_kv_client::AetherKvClient, +} + +impl KvClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_kv_client::AetherKvClient::new(inner), + } + } + + /// Put a key-value pair into the store. + pub async fn put( + &self, + key: impl Into>, + value: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .put(Request::new(pb::PutRequest { + key: key.into(), + value: value.into(), + lease: 0, + prev_kv: false, + })) + .await?; + Ok(resp.into_inner()) + } + + /// Create a [`PutBuilder`] for a Put request with optional parameters. + pub fn put_builder(&self, key: impl Into>, value: impl Into>) -> PutBuilder { + PutBuilder { + client: self.inner.clone(), + key: key.into(), + value: value.into(), + lease: 0, + prev_kv: false, + } + } + + /// Get a key from the store. + pub async fn get(&self, key: impl Into>) -> crate::Result { + let resp = self + .inner + .clone() + .get(Request::new(pb::GetRequest { + key: key.into(), + range_end: Vec::new(), + limit: 0, + revision: 0, + serializable: false, + sort_order: pb::SortOrder::None.into(), + sort_target: pb::SortTarget::Key.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Create a [`GetBuilder`] for a Get request with optional parameters. + pub fn get_builder(&self, key: impl Into>) -> GetBuilder { + GetBuilder { + client: self.inner.clone(), + key: key.into(), + range_end: Vec::new(), + limit: 0, + revision: 0, + serializable: false, + sort_order: pb::SortOrder::None.into(), + sort_target: pb::SortTarget::Key.into(), + } + } + + /// Delete a key from the store. + pub async fn delete(&self, key: impl Into>) -> crate::Result { + let resp = self + .inner + .clone() + .delete(Request::new(pb::DeleteRequest { + key: key.into(), + range_end: Vec::new(), + prev_kv: false, + })) + .await?; + Ok(resp.into_inner()) + } + + /// Create a [`DeleteBuilder`] for a Delete request with optional parameters. + pub fn delete_builder(&self, key: impl Into>) -> DeleteBuilder { + DeleteBuilder { + client: self.inner.clone(), + key: key.into(), + range_end: Vec::new(), + prev_kv: false, + } + } + + /// Range scan keys in [key, range_end). + pub async fn range( + &self, + key: impl Into>, + range_end: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .range(Request::new(pb::RangeRequest { + key: key.into(), + range_end: range_end.into(), + limit: 0, + revision: 0, + sort_order: pb::SortOrder::None.into(), + sort_target: pb::SortTarget::Key.into(), + serializable: false, + })) + .await?; + Ok(resp.into_inner()) + } + + /// Create a [`RangeBuilder`] for a Range request with optional parameters. + pub fn range_builder( + &self, + key: impl Into>, + range_end: impl Into>, + ) -> RangeBuilder { + RangeBuilder { + client: self.inner.clone(), + key: key.into(), + range_end: range_end.into(), + limit: 0, + revision: 0, + sort_order: pb::SortOrder::None.into(), + sort_target: pb::SortTarget::Key.into(), + serializable: false, + } + } + + /// Execute a transaction. + pub async fn txn(&self, request: pb::TxnRequest) -> crate::Result { + let resp = self.inner.clone().txn(Request::new(request)).await?; + Ok(resp.into_inner()) + } +} + +/// Builder for Put requests. +pub struct PutBuilder { + client: pb::aether_kv_client::AetherKvClient, + key: Vec, + value: Vec, + lease: i64, + prev_kv: bool, +} + +impl PutBuilder +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub fn lease(mut self, lease_id: i64) -> Self { + self.lease = lease_id; + self + } + + pub fn prev_kv(mut self) -> Self { + self.prev_kv = true; + self + } + + pub async fn send(mut self) -> crate::Result { + let resp = self + .client + .put(Request::new(pb::PutRequest { + key: self.key, + value: self.value, + lease: self.lease, + prev_kv: self.prev_kv, + })) + .await?; + Ok(resp.into_inner()) + } +} + +/// Builder for Get requests. +pub struct GetBuilder { + client: pb::aether_kv_client::AetherKvClient, + key: Vec, + range_end: Vec, + limit: i64, + revision: i64, + serializable: bool, + sort_order: i32, + sort_target: i32, +} + +impl GetBuilder +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub fn range_end(mut self, range_end: impl Into>) -> Self { + self.range_end = range_end.into(); + self + } + + pub fn limit(mut self, limit: i64) -> Self { + self.limit = limit; + self + } + + pub fn revision(mut self, revision: i64) -> Self { + self.revision = revision; + self + } + + pub fn serializable(mut self) -> Self { + self.serializable = true; + self + } + + pub fn sort(mut self, order: pb::SortOrder, target: pb::SortTarget) -> Self { + self.sort_order = order.into(); + self.sort_target = target.into(); + self + } + + pub async fn send(mut self) -> crate::Result { + let resp = self + .client + .get(Request::new(pb::GetRequest { + key: self.key, + range_end: self.range_end, + limit: self.limit, + revision: self.revision, + serializable: self.serializable, + sort_order: self.sort_order, + sort_target: self.sort_target, + })) + .await?; + Ok(resp.into_inner()) + } +} + +/// Builder for Delete requests. +pub struct DeleteBuilder { + client: pb::aether_kv_client::AetherKvClient, + key: Vec, + range_end: Vec, + prev_kv: bool, +} + +impl DeleteBuilder +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub fn range_end(mut self, range_end: impl Into>) -> Self { + self.range_end = range_end.into(); + self + } + + pub fn prev_kv(mut self) -> Self { + self.prev_kv = true; + self + } + + pub async fn send(mut self) -> crate::Result { + let resp = self + .client + .delete(Request::new(pb::DeleteRequest { + key: self.key, + range_end: self.range_end, + prev_kv: self.prev_kv, + })) + .await?; + Ok(resp.into_inner()) + } +} + +/// Builder for Range requests. +pub struct RangeBuilder { + client: pb::aether_kv_client::AetherKvClient, + key: Vec, + range_end: Vec, + limit: i64, + revision: i64, + sort_order: i32, + sort_target: i32, + serializable: bool, +} + +impl RangeBuilder +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub fn limit(mut self, limit: i64) -> Self { + self.limit = limit; + self + } + + pub fn revision(mut self, revision: i64) -> Self { + self.revision = revision; + self + } + + pub fn sort(mut self, order: pb::SortOrder, target: pb::SortTarget) -> Self { + self.sort_order = order.into(); + self.sort_target = target.into(); + self + } + + pub fn serializable(mut self) -> Self { + self.serializable = true; + self + } + + pub async fn send(mut self) -> crate::Result { + let resp = self + .client + .range(Request::new(pb::RangeRequest { + key: self.key, + range_end: self.range_end, + limit: self.limit, + revision: self.revision, + sort_order: self.sort_order, + sort_target: self.sort_target, + serializable: self.serializable, + })) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/lease.rs b/sdk/rust/src/lease.rs new file mode 100644 index 0000000..8466468 --- /dev/null +++ b/sdk/rust/src/lease.rs @@ -0,0 +1,135 @@ +use crate::pb; +use futures::StreamExt; +use std::time::Duration; +use tonic::{Request, Streaming}; + +pub use crate::keep_alive::KeepAliveHandle; + +/// Client for lease management operations. +pub struct LeaseClient { + inner: pb::aether_lease_client::AetherLeaseClient, +} + +impl LeaseClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_lease_client::AetherLeaseClient::new(inner), + } + } + + /// Create a new lease with the given TTL in seconds. + pub async fn grant(&self, ttl: i64) -> crate::Result { + let resp = self + .inner + .clone() + .lease_grant(Request::new(pb::LeaseGrantRequest { ttl })) + .await?; + Ok(resp.into_inner()) + } + + /// Revoke a lease and delete all attached keys. + pub async fn revoke(&self, id: i64) -> crate::Result { + let resp = self + .inner + .clone() + .lease_revoke(Request::new(pb::LeaseRevokeRequest { id })) + .await?; + Ok(resp.into_inner()) + } + + /// Query the remaining TTL of a lease. + pub async fn time_to_live( + &self, + id: i64, + keys: bool, + ) -> crate::Result { + let resp = self + .inner + .clone() + .lease_time_to_live(Request::new(pb::LeaseTimeToLiveRequest { id, keys })) + .await?; + Ok(resp.into_inner()) + } + + /// List all active leases. + pub async fn leases(&self) -> crate::Result { + let resp = self + .inner + .clone() + .lease_leases(Request::new(pb::LeaseLeasesRequest {})) + .await?; + Ok(resp.into_inner()) + } + + /// Open a keep-alive stream for a lease. + pub async fn keep_alive(&self, id: i64) -> crate::Result { + let (tx, rx) = tokio::sync::mpsc::channel(128); + let outbound = tokio_stream::wrappers::ReceiverStream::new(rx); + + let inbound = self + .inner + .clone() + .lease_keep_alive(Request::new(outbound)) + .await? + .into_inner(); + + Ok(LeaseKeepAlive { tx, inbound, id }) + } + + /// Start a background keep-alive task that periodically renews the lease. + /// + /// Returns a [`KeepAliveHandle`] that stops the task when dropped. + /// Call [`KeepAliveHandle::stop`] to gracefully shut down and await the task. + pub async fn start_keep_alive( + &self, + id: i64, + interval: Duration, + on_error: Option>, + ) -> crate::Result { + if interval.is_zero() { + return Err(crate::Error::InvalidArgument( + "keepalive interval must be non-zero".to_string(), + )); + } + + let ka = self.keep_alive(id).await?; + Ok(crate::keep_alive::spawn( + ka.tx, + ka.inbound, + interval, + move || pb::LeaseKeepAliveRequest { id }, + on_error, + )) + } +} + +/// A lease keep-alive stream. +pub struct LeaseKeepAlive { + tx: tokio::sync::mpsc::Sender, + inbound: Streaming, + id: i64, +} + +impl LeaseKeepAlive { + /// Send a keep-alive request. + pub async fn send(&self) -> crate::Result<()> { + self.tx + .send(pb::LeaseKeepAliveRequest { id: self.id }) + .await + .map_err(|_| crate::Error::Grpc(tonic::Status::internal("keep-alive stream closed"))) + } + + /// Receive a keep-alive response. + pub async fn recv(&mut self) -> crate::Result> { + match self.inbound.next().await { + Some(Ok(resp)) => Ok(Some(resp)), + Some(Err(e)) => Err(crate::Error::Grpc(e)), + None => Ok(None), + } + } +} diff --git a/sdk/rust/src/lib.rs b/sdk/rust/src/lib.rs new file mode 100644 index 0000000..61abb4a --- /dev/null +++ b/sdk/rust/src/lib.rs @@ -0,0 +1,254 @@ +pub mod auth; +pub mod barrier; +pub mod cluster; +pub mod election; +pub mod keep_alive; +pub mod kv; +pub mod lease; +pub mod lock; +pub mod maintenance; +pub mod queue; +pub mod session; +pub mod shard; +pub mod watch; + +pub mod pb { + tonic::include_proto!("aether"); +} + +use std::time::Duration; +use tonic::codegen::InterceptedService; +use tonic::transport::{Channel, ClientTlsConfig}; + +/// Error type for the Aether client SDK. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("gRPC error: {0}")] + Grpc(#[from] tonic::Status), + + #[error("transport error: {0}")] + Transport(#[from] tonic::transport::Error), + + #[error("invalid endpoint: {0}")] + InvalidEndpoint(String), + + #[error("invalid argument: {0}")] + InvalidArgument(String), + + #[error("at least one endpoint is required")] + NoEndpoints, + + #[error("connection timed out")] + ConnectTimeout, +} + +pub type Result = std::result::Result; + +/// Configuration for creating a [`Client`]. +pub struct Config { + /// Aether server endpoints (host:port). + /// + /// Currently only the first endpoint is used. + // TODO: implement endpoint load balancing / failover. + pub endpoints: Vec, + /// TLS configuration. If `None`, plaintext is used. + pub tls: Option, + /// Timeout for establishing the gRPC connection. Default: 5s. + pub dial_timeout: Duration, + /// Optional authentication token sent as `authorization` metadata. + pub token: Option, +} + +impl Config { + pub fn new(endpoints: Vec) -> Self { + Self { + endpoints, + tls: None, + dial_timeout: Duration::from_secs(5), + token: None, + } + } + + pub fn with_tls(mut self, tls: ClientTlsConfig) -> Self { + self.tls = Some(tls); + self + } + + pub fn with_dial_timeout(mut self, timeout: Duration) -> Self { + self.dial_timeout = timeout; + self + } + + pub fn with_token(mut self, token: impl Into) -> Self { + self.token = Some(token.into()); + self + } +} + +/// gRPC interceptor that attaches an authentication token to every request. +#[derive(Clone)] +pub struct AuthInterceptor { + token: String, +} + +impl tonic::service::Interceptor for AuthInterceptor { + fn call( + &mut self, + mut req: tonic::Request<()>, + ) -> std::result::Result, tonic::Status> { + req.metadata_mut().insert( + "authorization", + tonic::metadata::MetadataValue::try_from(format!("Bearer {}", self.token)) + .map_err(|_| tonic::Status::internal("invalid auth token"))?, + ); + Ok(req) + } +} + +/// Entry point for interacting with an Aether cluster. +/// +/// The type parameter `T` is the inner gRPC transport type. +/// Use [`Client::connect`] for an unauthenticated client, or +/// [`Client::connect_with_token`] for one that sends an auth token. +pub struct Client { + kv: kv::KvClient, + watch: watch::WatchClient, + lease: lease::LeaseClient, + auth: auth::AuthClient, + lock: lock::LockClient, + election: election::ElectionClient, + session: session::SessionClient, + cluster: cluster::ClusterClient, + maintenance: maintenance::MaintenanceClient, + barrier: barrier::BarrierClient, + queue: queue::QueueClient, + shard: shard::ShardClient, +} + +impl Client { + /// Connect without authentication. + pub async fn connect(cfg: Config) -> Result { + let channel = Self::build_channel(&cfg).await?; + Ok(Self::from_channel(channel)) + } + + /// Create a client from an existing channel (no auth). + pub fn from_channel(channel: Channel) -> Self { + Self { + kv: kv::KvClient::new(channel.clone()), + watch: watch::WatchClient::new(channel.clone()), + lease: lease::LeaseClient::new(channel.clone()), + auth: auth::AuthClient::new(channel.clone()), + lock: lock::LockClient::new(channel.clone()), + election: election::ElectionClient::new(channel.clone()), + session: session::SessionClient::new(channel.clone()), + cluster: cluster::ClusterClient::new(channel.clone()), + maintenance: maintenance::MaintenanceClient::new(channel.clone()), + barrier: barrier::BarrierClient::new(channel.clone()), + queue: queue::QueueClient::new(channel.clone()), + shard: shard::ShardClient::new(channel), + } + } +} + +impl Client> { + /// Connect with an authentication token. + pub async fn connect_with_token(cfg: Config) -> Result { + let token = cfg + .token + .clone() + .ok_or(Error::InvalidArgument("token is required".to_string()))?; + let channel = Self::build_channel(&cfg).await?; + let interceptor = AuthInterceptor { token }; + Ok(Self::from_intercepted_channel(channel, interceptor)) + } + + /// Create a client from a channel and an auth interceptor. + pub fn from_intercepted_channel(channel: Channel, interceptor: AuthInterceptor) -> Self { + let svc = InterceptedService::new(channel, interceptor); + Self { + kv: kv::KvClient::new(svc.clone()), + watch: watch::WatchClient::new(svc.clone()), + lease: lease::LeaseClient::new(svc.clone()), + auth: auth::AuthClient::new(svc.clone()), + lock: lock::LockClient::new(svc.clone()), + election: election::ElectionClient::new(svc.clone()), + session: session::SessionClient::new(svc.clone()), + cluster: cluster::ClusterClient::new(svc.clone()), + maintenance: maintenance::MaintenanceClient::new(svc.clone()), + barrier: barrier::BarrierClient::new(svc.clone()), + queue: queue::QueueClient::new(svc.clone()), + shard: shard::ShardClient::new(svc), + } + } +} + +impl Client { + pub fn kv(&self) -> &kv::KvClient { + &self.kv + } + + pub fn watch(&self) -> &watch::WatchClient { + &self.watch + } + + pub fn lease(&self) -> &lease::LeaseClient { + &self.lease + } + + pub fn auth(&self) -> &auth::AuthClient { + &self.auth + } + + pub fn lock(&self) -> &lock::LockClient { + &self.lock + } + + pub fn election(&self) -> &election::ElectionClient { + &self.election + } + + pub fn session(&self) -> &session::SessionClient { + &self.session + } + + pub fn cluster(&self) -> &cluster::ClusterClient { + &self.cluster + } + + pub fn maintenance(&self) -> &maintenance::MaintenanceClient { + &self.maintenance + } + + pub fn barrier(&self) -> &barrier::BarrierClient { + &self.barrier + } + + pub fn queue(&self) -> &queue::QueueClient { + &self.queue + } + + pub fn shard(&self) -> &shard::ShardClient { + &self.shard + } + + // Placed here (not in a Channel-specific impl) so both + // `Client` and `Client>` can call it. + async fn build_channel(cfg: &Config) -> Result { + let endpoint = cfg.endpoints.first().ok_or(Error::NoEndpoints)?; + let scheme = if cfg.tls.is_some() { "https" } else { "http" }; + let uri: tonic::transport::Uri = format!("{}://{}", scheme, endpoint) + .parse() + .map_err(|_| Error::InvalidEndpoint(format!("invalid endpoint: {}", endpoint)))?; + + let mut channel = Channel::builder(uri); + if let Some(tls) = &cfg.tls { + channel = channel.tls_config(tls.clone())?; + } + let channel = tokio::time::timeout(cfg.dial_timeout, channel.connect()) + .await + .map_err(|_| Error::ConnectTimeout)??; + + Ok(channel) + } +} diff --git a/sdk/rust/src/lock.rs b/sdk/rust/src/lock.rs new file mode 100644 index 0000000..78c5524 --- /dev/null +++ b/sdk/rust/src/lock.rs @@ -0,0 +1,57 @@ +use crate::pb; +use tonic::Request; + +/// Client for distributed lock operations. +pub struct LockClient { + inner: pb::aether_lock_client::AetherLockClient, +} + +impl LockClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_lock_client::AetherLockClient::new(inner), + } + } + + /// Acquire a distributed lock with the given name and lease. + pub async fn lock( + &self, + name: impl Into>, + lease_id: i64, + ) -> crate::Result { + let resp = self + .inner + .clone() + .lock(Request::new(pb::LockRequest { + name: name.into(), + lease_id, + })) + .await?; + Ok(resp.into_inner()) + } + + /// Release a distributed lock by key. + pub async fn unlock(&self, key: impl Into>) -> crate::Result { + let resp = self + .inner + .clone() + .unlock(Request::new(pb::UnlockRequest { key: key.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// Query information about a lock. + pub async fn query(&self, name: impl Into>) -> crate::Result { + let resp = self + .inner + .clone() + .lock_query(Request::new(pb::LockQueryRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/maintenance.rs b/sdk/rust/src/maintenance.rs new file mode 100644 index 0000000..025498e --- /dev/null +++ b/sdk/rust/src/maintenance.rs @@ -0,0 +1,59 @@ +use crate::pb; +use tonic::Request; + +/// Client for cluster maintenance operations. +pub struct MaintenanceClient { + inner: pb::aether_maintenance_client::AetherMaintenanceClient, +} + +impl MaintenanceClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_maintenance_client::AetherMaintenanceClient::new(inner), + } + } + + /// Trigger storage defragmentation. + pub async fn defrag(&self) -> crate::Result { + let resp = self + .inner + .clone() + .defrag(Request::new(pb::DefragRequest {})) + .await?; + Ok(resp.into_inner()) + } + + /// Manage cluster alarms. + pub async fn alarm( + &self, + action: pb::AlarmAction, + alarm_type: pb::AlarmType, + member_id: u64, + ) -> crate::Result { + let resp = self + .inner + .clone() + .alarm(Request::new(pb::AlarmRequest { + action: action.into(), + alarm: alarm_type.into(), + member_id, + })) + .await?; + Ok(resp.into_inner()) + } + + /// Get node status. + pub async fn status(&self) -> crate::Result { + let resp = self + .inner + .clone() + .status(Request::new(pb::StatusRequest {})) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/queue.rs b/sdk/rust/src/queue.rs new file mode 100644 index 0000000..8870d91 --- /dev/null +++ b/sdk/rust/src/queue.rs @@ -0,0 +1,60 @@ +use crate::pb; +use tonic::Request; + +/// Client for distributed FIFO queue operations. +pub struct QueueClient { + inner: pb::aether_queue_client::AetherQueueClient, +} + +impl QueueClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_queue_client::AetherQueueClient::new(inner), + } + } + + /// Add an item to the named queue. + pub async fn enqueue( + &self, + name: impl Into>, + value: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .enqueue(Request::new(pb::QueueEnqueueRequest { + name: name.into(), + value: value.into(), + })) + .await?; + Ok(resp.into_inner()) + } + + /// Remove and return the front item from the named queue. + pub async fn dequeue( + &self, + name: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .dequeue(Request::new(pb::QueueDequeueRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// Peek at the front item without removing it. + pub async fn peek(&self, name: impl Into>) -> crate::Result { + let resp = self + .inner + .clone() + .peek(Request::new(pb::QueuePeekRequest { name: name.into() })) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/session.rs b/sdk/rust/src/session.rs new file mode 100644 index 0000000..b2538db --- /dev/null +++ b/sdk/rust/src/session.rs @@ -0,0 +1,121 @@ +use crate::pb; +use futures::StreamExt; +use std::time::Duration; +use tonic::{Request, Streaming}; + +pub use crate::keep_alive::KeepAliveHandle; + +/// Client for session management operations. +pub struct SessionClient { + inner: pb::aether_session_client::AetherSessionClient, +} + +impl SessionClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_session_client::AetherSessionClient::new(inner), + } + } + + /// Create a new session with the requested TTL. + pub async fn create(&self, ttl: i64) -> crate::Result { + let resp = self + .inner + .clone() + .create(Request::new(pb::SessionCreateRequest { ttl })) + .await?; + Ok(resp.into_inner()) + } + + /// Close a session and delete all attached keys. + pub async fn close(&self, id: i64) -> crate::Result { + let resp = self + .inner + .clone() + .close(Request::new(pb::SessionCloseRequest { id })) + .await?; + Ok(resp.into_inner()) + } + + /// Query session information. + pub async fn query(&self, id: i64) -> crate::Result { + let resp = self + .inner + .clone() + .query(Request::new(pb::SessionQueryRequest { id })) + .await?; + Ok(resp.into_inner()) + } + + /// Open a keep-alive stream for a session. + pub async fn keep_alive(&self, id: i64) -> crate::Result { + let (tx, rx) = tokio::sync::mpsc::channel(128); + let outbound = tokio_stream::wrappers::ReceiverStream::new(rx); + + let inbound = self + .inner + .clone() + .keep_alive(Request::new(outbound)) + .await? + .into_inner(); + + Ok(SessionKeepAlive { tx, inbound, id }) + } + + /// Start a background keep-alive task that periodically renews the session. + /// + /// Returns a [`KeepAliveHandle`] that stops the task when dropped. + /// Call [`KeepAliveHandle::stop`] to gracefully shut down and await the task. + pub async fn start_keep_alive( + &self, + id: i64, + interval: Duration, + on_error: Option>, + ) -> crate::Result { + if interval.is_zero() { + return Err(crate::Error::InvalidArgument( + "keepalive interval must be non-zero".to_string(), + )); + } + + let ka = self.keep_alive(id).await?; + Ok(crate::keep_alive::spawn( + ka.tx, + ka.inbound, + interval, + move || pb::SessionKeepAliveRequest { id }, + on_error, + )) + } +} + +/// A session keep-alive stream. +pub struct SessionKeepAlive { + tx: tokio::sync::mpsc::Sender, + inbound: Streaming, + id: i64, +} + +impl SessionKeepAlive { + /// Send a keep-alive request. + pub async fn send(&self) -> crate::Result<()> { + self.tx + .send(pb::SessionKeepAliveRequest { id: self.id }) + .await + .map_err(|_| crate::Error::Grpc(tonic::Status::internal("keep-alive stream closed"))) + } + + /// Receive a keep-alive response. + pub async fn recv(&mut self) -> crate::Result> { + match self.inbound.next().await { + Some(Ok(resp)) => Ok(Some(resp)), + Some(Err(e)) => Err(crate::Error::Grpc(e)), + None => Ok(None), + } + } +} diff --git a/sdk/rust/src/shard.rs b/sdk/rust/src/shard.rs new file mode 100644 index 0000000..1abe014 --- /dev/null +++ b/sdk/rust/src/shard.rs @@ -0,0 +1,60 @@ +use crate::pb; +use tonic::Request; + +/// Client for data sharding operations. +pub struct ShardClient { + inner: pb::aether_shard_client::AetherShardClient, +} + +impl ShardClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_shard_client::AetherShardClient::new(inner), + } + } + + /// Find the region owning a key. + pub async fn get_region( + &self, + key: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .get_region(Request::new(pb::GetRegionRequest { key: key.into() })) + .await?; + Ok(resp.into_inner()) + } + + /// List all regions. + pub async fn list_regions(&self) -> crate::Result { + let resp = self + .inner + .clone() + .list_regions(Request::new(pb::ListRegionsRequest {})) + .await?; + Ok(resp.into_inner()) + } + + /// Split a region at the given key. + pub async fn split_region( + &self, + region_id: u64, + split_key: impl Into>, + ) -> crate::Result { + let resp = self + .inner + .clone() + .split_region(Request::new(pb::SplitRegionRequest { + region_id, + split_key: split_key.into(), + })) + .await?; + Ok(resp.into_inner()) + } +} diff --git a/sdk/rust/src/watch.rs b/sdk/rust/src/watch.rs new file mode 100644 index 0000000..fc0fa9d --- /dev/null +++ b/sdk/rust/src/watch.rs @@ -0,0 +1,80 @@ +use crate::pb; +use tonic::{Request, Streaming}; + +/// Client for watch operations. +pub struct WatchClient { + inner: pb::aether_watch_client::AetherWatchClient, +} + +impl WatchClient +where + T: tonic::client::GrpcService + Clone, + T::ResponseBody: tonic::codegen::Body + Send + 'static, + ::Error: Into + Send, +{ + pub(crate) fn new(inner: T) -> Self { + Self { + inner: pb::aether_watch_client::AetherWatchClient::new(inner), + } + } + + /// Create a bidirectional watch stream. + pub async fn watch(&self) -> crate::Result { + let (tx, rx) = tokio::sync::mpsc::channel(128); + let outbound = tokio_stream::wrappers::ReceiverStream::new(rx); + + let inbound = self + .inner + .clone() + .watch(Request::new(outbound)) + .await? + .into_inner(); + + Ok(WatchStream { tx, inbound }) + } +} + +/// A bidirectional watch stream. +pub struct WatchStream { + tx: tokio::sync::mpsc::Sender, + inbound: Streaming, +} + +impl WatchStream { + /// Send a watch create request. + /// + /// The server responds asynchronously via [`recv`](Self::recv). + /// Call `recv` after `create` to obtain the `watch_id` from the response. + pub async fn create(&self, req: pb::WatchCreateRequest) -> crate::Result<()> { + let req = pb::WatchRequest { + request: Some(pb::watch_request::Request::Create(req)), + }; + self.tx + .send(req) + .await + .map_err(|_| crate::Error::Grpc(tonic::Status::internal("watch stream closed"))) + } + + /// Cancel a watch by ID. + pub async fn cancel(&self, watch_id: i64) -> crate::Result<()> { + let req = pb::WatchRequest { + request: Some(pb::watch_request::Request::Cancel(pb::WatchCancelRequest { + watch_id, + })), + }; + self.tx + .send(req) + .await + .map_err(|_| crate::Error::Grpc(tonic::Status::internal("watch stream closed"))) + } + + /// Receive the next watch response. + pub async fn recv(&mut self) -> crate::Result> { + use futures::StreamExt; + match self.inbound.next().await { + Some(Ok(resp)) => Ok(Some(resp)), + Some(Err(e)) => Err(crate::Error::Grpc(e)), + None => Ok(None), + } + } +}