sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit 03c568672034f9d33e896c9585eaea2362c664d6
parent 9aa7d727eff8cc342fe698f53903e1339772ebf0
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 10:12:14 +0000

sdk: align package manifest and module root

- declare the final required and optional lower package graph
- replace legacy feature aliases with the governed vocabulary
- establish the eleven-module SDK ownership skeleton
- verify default and all-feature package boundaries

Diffstat:
M.cargo/config.toml | 4++++
MCargo.lock | 939+++++++++++++++----------------------------------------------------------------
MCargo.toml | 4++++
Mcrates/sdk/Cargo.toml | 242++++++++++++++-----------------------------------------------------------------
Acrates/sdk/src/capability.rs | 1+
Acrates/sdk/src/client.rs | 1+
Acrates/sdk/src/diagnostics.rs | 1+
Mcrates/sdk/src/error.rs | 1350+------------------------------------------------------------------------------
Mcrates/sdk/src/farm.rs | 10+---------
Mcrates/sdk/src/lib.rs | 213++++++-------------------------------------------------------------------------
Acrates/sdk/src/listing.rs | 1+
Acrates/sdk/src/signing.rs | 1+
Acrates/sdk/src/storage.rs | 1+
Acrates/sdk/src/sync.rs | 1+
Acrates/sdk/src/trade.rs | 1+
Mcrates/sdk/src/transport.rs | 774+------------------------------------------------------------------------------
Acrates/sdk/tests/package_boundary.rs | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
17 files changed, 320 insertions(+), 3296 deletions(-)

diff --git a/.cargo/config.toml b/.cargo/config.toml @@ -12,6 +12,7 @@ radroots_event = { path = "../lib/crates/event" } radroots_event_codec = { path = "../lib/crates/event_codec" } radroots_event_index = { path = "../lib/crates/event_index" } radroots_geocoder = { path = "../lib/crates/geocoder" } +radroots_geonames = { path = "../lib/crates/geonames" } radroots_identity = { path = "../lib/crates/identity" } radroots_nostr = { path = "../lib/crates/nostr" } radroots_nostr_connect = { path = "../lib/crates/nostr_connect" } @@ -23,6 +24,9 @@ radroots_protected_store = { path = "../lib/crates/protected_store" } radroots_secret_vault = { path = "../lib/crates/secret_vault" } radroots_secrets = { path = "../lib/crates/secrets" } radroots_signing = { path = "../lib/crates/signing" } +radroots_storage = { path = "../lib/crates/storage" } +radroots_storage_sqlite = { path = "../lib/crates/storage_sqlite" } +radroots_sync = { path = "../lib/crates/sync" } radroots_transport = { path = "../lib/crates/transport" } radroots_transport_nostr = { path = "../lib/crates/transport_nostr" } radroots_transport_reticulum = { path = "../lib/crates/transport_reticulum" } diff --git a/Cargo.lock b/Cargo.lock @@ -30,27 +30,6 @@ dependencies = [ ] [[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -72,12 +51,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -160,12 +133,6 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" @@ -215,9 +182,6 @@ name = "bitflags" version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" -dependencies = [ - "serde_core", -] [[package]] name = "block-buffer" @@ -344,60 +308,12 @@ dependencies = [ ] [[package]] -name = "config" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" -dependencies = [ - "async-trait", - "convert_case", - "json5", - "nom", - "pathdiff", - "ron", - "rust-ini", - "serde", - "serde_json", - "toml", - "yaml-rust2", -] - -[[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -437,30 +353,6 @@ dependencies = [ ] [[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -476,12 +368,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] name = "crypto-bigint" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -521,12 +407,6 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -549,15 +429,6 @@ dependencies = [ ] [[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - -[[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -647,27 +518,6 @@ dependencies = [ ] [[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -695,6 +545,18 @@ dependencies = [ ] [[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -769,6 +631,16 @@ dependencies = [ ] [[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] name = "futures" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -776,7 +648,6 @@ checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", - "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -828,17 +699,6 @@ 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" @@ -859,7 +719,6 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", - "futures-macro", "futures-sink", "futures-task", "memchr", @@ -943,16 +802,6 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" @@ -979,15 +828,6 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "hashlink" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" @@ -1017,52 +857,6 @@ dependencies = [ ] [[package]] -name = "hickory-proto" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 1.1.0", - "ipnet", - "once_cell", - "rand 0.9.4", - "ring", - "thiserror 2.0.18", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "moka", - "once_cell", - "parking_lot", - "rand 0.9.4", - "resolv-conf", - "smallvec", - "thiserror 2.0.18", - "tokio", - "tracing", -] - -[[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1152,7 +946,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -1347,19 +1141,6 @@ dependencies = [ ] [[package]] -name = "ipconfig" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" -dependencies = [ - "socket2", - "widestring", - "windows-registry", - "windows-result", - "windows-sys 0.61.2", -] - -[[package]] name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1389,17 +1170,6 @@ dependencies = [ ] [[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - -[[package]] name = "k256" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1410,12 +1180,6 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1484,15 +1248,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] name = "mediatype" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1515,12 +1270,6 @@ dependencies = [ ] [[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1542,46 +1291,19 @@ dependencies = [ ] [[package]] -name = "moka" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" -dependencies = [ - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "equivalent", - "parking_lot", - "portable-atomic", - "smallvec", - "tagptr", - "uuid", -] - -[[package]] name = "negentropy" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] name = "nostr" version = "0.44.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d8f0fe13526800300a36bf3b7c5f752e62e32ab81c74a8e5caa2865708625a" dependencies = [ "aes", - "base64 0.22.1", + "base64", "bech32", "bip39", "bitcoin_hashes", @@ -1663,12 +1385,6 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1683,10 +1399,6 @@ name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -dependencies = [ - "critical-section", - "portable-atomic", -] [[package]] name = "oorandom" @@ -1701,16 +1413,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] -name = "ordered-multimap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" -dependencies = [ - "dlv-list", - "hashbrown 0.14.5", -] - -[[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1751,12 +1453,6 @@ dependencies = [ ] [[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] name = "pbkdf2" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1773,49 +1469,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "pest" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" -dependencies = [ - "pest", - "sha2", -] - -[[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1839,12 +1492,6 @@ dependencies = [ ] [[package]] -name = "portable-atomic" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" - -[[package]] name = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1854,12 +1501,6 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2068,37 +1709,13 @@ dependencies = [ ] [[package]] -name = "radroots_event_store" +name = "radroots_geonames" version = "0.1.0-alpha" dependencies = [ - "getrandom 0.2.17", - "hex", - "radroots_blossom", - "radroots_event", - "radroots_event_codec", - "radroots_identity", - "radroots_transport", - "serde", - "serde_json", + "fs2", + "rusqlite", "sha2", - "sqlx", - "thiserror 1.0.69", -] - -[[package]] -name = "radroots_geocoder" -version = "0.1.0-alpha" -dependencies = [ - "futures-executor", - "hex", - "radroots_runtime_paths", - "reqwest", - "serde", - "sha2", - "sqlx", "tempfile", - "thiserror 1.0.69", - "tokio", "url", ] @@ -2120,18 +1737,6 @@ dependencies = [ ] [[package]] -name = "radroots_log" -version = "0.1.0-alpha" -dependencies = [ - "chrono", - "serde_json", - "thiserror 1.0.69", - "tracing", - "tracing-appender", - "tracing-subscriber", -] - -[[package]] name = "radroots_nostr" version = "0.1.0-alpha" dependencies = [ @@ -2161,52 +1766,6 @@ dependencies = [ ] [[package]] -name = "radroots_nostr_signer" -version = "0.1.0-alpha" -dependencies = [ - "hex", - "nostr", - "radroots_identity", - "radroots_nostr", - "radroots_nostr_connect", - "radroots_runtime", - "serde", - "serde_json", - "sha2", - "thiserror 1.0.69", - "url", - "uuid", -] - -[[package]] -name = "radroots_outbox" -version = "0.1.0-alpha" -dependencies = [ - "hex", - "radroots_event", - "radroots_event_store", - "radroots_protocol", - "radroots_transport", - "serde", - "serde_json", - "sha2", - "sqlx", - "thiserror 1.0.69", -] - -[[package]] -name = "radroots_protected_store" -version = "0.1.0-alpha" -dependencies = [ - "chacha20poly1305", - "getrandom 0.2.17", - "radroots_secret_vault", - "serde", - "serde_json", - "zeroize", -] - -[[package]] name = "radroots_protocol" version = "0.1.0-alpha" dependencies = [ @@ -2270,114 +1829,64 @@ dependencies = [ name = "radroots_replica_sync" version = "0.1.0-alpha" dependencies = [ - "base64 0.22.1", + "base64", "hex", "radroots_core", "radroots_event", "radroots_event_codec", "radroots_replica_schema", - "radroots_replica_store", - "radroots_sql_core", - "serde", - "serde_json", - "sha2", - "uuid", -] - -[[package]] -name = "radroots_replica_sync_wasm" -version = "0.1.0-alpha" -dependencies = [ - "base64 0.22.1", - "radroots_event", - "radroots_replica_sync", - "radroots_sdk_sql_wasm_runtime", - "serde", - "serde-wasm-bindgen", - "serde_json", - "uuid", - "wasm-bindgen", -] - -[[package]] -name = "radroots_runtime" -version = "0.1.0-alpha" -dependencies = [ - "anyhow", - "chacha20poly1305", - "config", - "getrandom 0.2.17", - "radroots_log", - "radroots_protected_store", - "radroots_runtime_paths", - "radroots_secret_vault", - "serde", - "serde_json", - "tempfile", - "thiserror 1.0.69", - "tokio", - "toml", - "tracing", - "zeroize", + "radroots_replica_store", + "radroots_sql_core", + "serde", + "serde_json", + "sha2", + "uuid", ] [[package]] -name = "radroots_runtime_contract_v1" +name = "radroots_replica_sync_wasm" version = "0.1.0-alpha" dependencies = [ - "radroots_protocol_contract_v1", + "base64", + "radroots_event", + "radroots_replica_sync", + "radroots_sdk_sql_wasm_runtime", "serde", + "serde-wasm-bindgen", + "serde_json", + "uuid", + "wasm-bindgen", ] [[package]] -name = "radroots_runtime_paths" +name = "radroots_runtime_contract_v1" version = "0.1.0-alpha" dependencies = [ + "radroots_protocol_contract_v1", "serde", - "thiserror 1.0.69", ] [[package]] name = "radroots_sdk" version = "0.1.0-alpha" dependencies = [ - "base64 0.22.1", - "futures", - "hex", - "nostr", "radroots_core", "radroots_event", "radroots_event_codec", - "radroots_event_store", - "radroots_geocoder", + "radroots_geonames", "radroots_identity", "radroots_nostr", "radroots_nostr_connect", - "radroots_nostr_signer", - "radroots_outbox", - "radroots_protected_store", "radroots_protocol", - "radroots_replica_schema", - "radroots_replica_store", - "radroots_replica_sync", - "radroots_runtime_paths", - "radroots_secret_vault", "radroots_secrets", "radroots_signing", - "radroots_sql_core", + "radroots_storage", + "radroots_storage_sqlite", + "radroots_sync", "radroots_trade", "radroots_transport", "radroots_transport_nostr", - "radroots_transport_reticulum", "reqwest", - "serde", - "serde_json", - "sha2", - "sqlx", - "tempfile", - "tokio", - "tokio-tungstenite", - "uuid", ] [[package]] @@ -2415,10 +1924,6 @@ dependencies = [ ] [[package]] -name = "radroots_secret_vault" -version = "0.1.0-alpha" - -[[package]] name = "radroots_secrets" version = "0.1.0-alpha" dependencies = [ @@ -2440,18 +1945,52 @@ name = "radroots_sql_core" version = "0.1.0-alpha" dependencies = [ "chrono", - "futures-executor", "serde", "serde_json", - "sqlx", "uuid", ] [[package]] +name = "radroots_storage" +version = "0.1.0-alpha" +dependencies = [ + "radroots_event", + "radroots_protocol", + "radroots_trade", + "radroots_transport", +] + +[[package]] +name = "radroots_storage_sqlite" +version = "0.1.0-alpha" +dependencies = [ + "fs2", + "radroots_event_codec", + "radroots_secrets", + "radroots_storage", + "sha2", + "sqlx", +] + +[[package]] +name = "radroots_sync" +version = "0.1.0-alpha" +dependencies = [ + "radroots_event", + "radroots_event_codec", + "radroots_protocol", + "radroots_signing", + "radroots_storage", + "radroots_trade", + "radroots_transport", + "sha2", +] + +[[package]] name = "radroots_trade" version = "0.1.0-alpha" dependencies = [ - "base64 0.22.1", + "base64", "hex", "radroots_core", "radroots_event", @@ -2473,7 +2012,6 @@ dependencies = [ "radroots_event", "radroots_identity", "radroots_protocol", - "serde", "sha2", ] @@ -2481,29 +2019,20 @@ dependencies = [ name = "radroots_transport_nostr" version = "0.1.0-alpha" dependencies = [ - "futures", - "nostr", + "async-wsocket", + "nostr-relay-pool", "nostr-sdk", - "radroots_event", - "radroots_event_store", + "radroots_event_codec", "radroots_nostr", - "radroots_outbox", + "radroots_protocol", "radroots_transport", - "serde", "serde_json", - "thiserror 1.0.69", "tokio", + "tokio-tungstenite", "url", ] [[package]] -name = "radroots_transport_reticulum" -version = "0.1.0-alpha" -dependencies = [ - "radroots_transport", -] - -[[package]] name = "rand" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2572,32 +2101,14 @@ dependencies = [ ] [[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 = "reqwest" version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", - "hickory-resolver", "http", "http-body", "http-body-util", @@ -2606,7 +2117,6 @@ dependencies = [ "hyper-util", "js-sys", "log", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -2629,12 +2139,6 @@ dependencies = [ ] [[package]] -name = "resolv-conf" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" - -[[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2649,25 +2153,27 @@ dependencies = [ ] [[package]] -name = "ron" -version = "0.8.1" +name = "rsqlite-vfs" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ - "base64 0.21.7", - "bitflags", - "serde", - "serde_derive", + "hashbrown 0.16.1", + "thiserror 2.0.18", ] [[package]] -name = "rust-ini" -version = "0.20.0" +name = "rusqlite" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" +checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" dependencies = [ - "cfg-if", - "ordered-multimap", + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -2921,31 +2427,12 @@ dependencies = [ ] [[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] name = "shlex" version = "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 = "simd-adler32" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2983,6 +2470,18 @@ dependencies = [ ] [[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] name = "sqlx" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2999,7 +2498,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "cfg-if", "crc", @@ -3011,7 +2510,7 @@ dependencies = [ "futures-io", "futures-util", "hashbrown 0.16.1", - "hashlink 0.11.1", + "hashlink", "indexmap", "log", "memchr", @@ -3099,12 +2598,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "symlink" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" - -[[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3136,12 +2629,6 @@ dependencies = [ ] [[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] name = "tar" version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3206,54 +2693,6 @@ dependencies = [ ] [[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] name = "tinystr" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3288,7 +2727,6 @@ dependencies = [ "libc", "mio", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -3453,19 +2891,6 @@ dependencies = [ ] [[package]] -name = "tracing-appender" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" -dependencies = [ - "crossbeam-channel", - "symlink", - "thiserror 2.0.18", - "time", - "tracing-subscriber", -] - -[[package]] name = "tracing-attributes" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3483,36 +2908,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] @@ -3547,12 +2942,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] name = "unicode-bidi" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3580,12 +2969,6 @@ dependencies = [ ] [[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3656,12 +3039,6 @@ dependencies = [ ] [[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3884,10 +3261,20 @@ dependencies = [ ] [[package]] -name = "widestring" -version = "1.2.1" +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" @@ -3899,6 +3286,12 @@ dependencies = [ ] [[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3940,17 +3333,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4244,17 +3626,6 @@ dependencies = [ ] [[package]] -name = "yaml-rust2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink 0.8.4", -] - -[[package]] name = "yoke" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4362,3 +3733,35 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[patch.unused]] +name = "radroots_event_store" +version = "0.1.0-alpha" + +[[patch.unused]] +name = "radroots_geocoder" +version = "0.1.0-alpha" + +[[patch.unused]] +name = "radroots_nostr_signer" +version = "0.1.0-alpha" + +[[patch.unused]] +name = "radroots_outbox" +version = "0.1.0-alpha" + +[[patch.unused]] +name = "radroots_protected_store" +version = "0.1.0-alpha" + +[[patch.unused]] +name = "radroots_runtime_paths" +version = "0.1.0-alpha" + +[[patch.unused]] +name = "radroots_secret_vault" +version = "0.1.0-alpha" + +[[patch.unused]] +name = "radroots_transport_reticulum" +version = "0.1.0-alpha" diff --git a/Cargo.toml b/Cargo.toml @@ -55,6 +55,7 @@ radroots_event = { package = "radroots_event", version = "=0.1.0-alpha", default radroots_event_codec = { package = "radroots_event_codec", version = "=0.1.0-alpha", default-features = false } radroots_event_index = { version = "=0.1.0-alpha", default-features = false } radroots_geocoder = { version = "=0.1.0-alpha" } +radroots_geonames = { package = "radroots_geonames", version = "=0.1.0-alpha", default-features = false } radroots_identity = { package = "radroots_identity", version = "=0.1.0-alpha", default-features = false, features = [ "std", ] } @@ -69,6 +70,9 @@ radroots_runtime_contract_v1 = { path = "crates/runtime_contract_v1", version = radroots_secret_vault = { version = "=0.1.0-alpha", default-features = false } radroots_secrets = { package = "radroots_secrets", version = "=0.1.0-alpha", default-features = false } radroots_signing = { package = "radroots_signing", version = "=0.1.0-alpha", default-features = false } +radroots_storage = { package = "radroots_storage", version = "=0.1.0-alpha", default-features = false } +radroots_storage_sqlite = { package = "radroots_storage_sqlite", version = "=0.1.0-alpha", default-features = false } +radroots_sync = { package = "radroots_sync", version = "=0.1.0-alpha", default-features = false } radroots_transport = { package = "radroots_transport", version = "=0.1.0-alpha", default-features = false } radroots_transport_nostr = { package = "radroots_transport_nostr", version = "=0.1.0-alpha", default-features = false } radroots_transport_reticulum = { version = "=0.1.0-alpha", default-features = false } diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -6,236 +6,80 @@ edition.workspace = true authors.workspace = true rust-version.workspace = true license.workspace = true -description = "Public Rust SDK for Radroots" +description = "Host-neutral asynchronous client engine for Radroots" repository.workspace = true homepage.workspace = true readme = "README.md" +autotests = false +autoexamples = false [lib] name = "radroots_sdk" [features] -default = ["std", "serde", "serde_json", "identity-models"] -std = [ - "radroots_event/std", - "radroots_event_codec/std", - "radroots_trade/std", -] -serde = [ - "dep:serde", - "radroots_event/serde", - "radroots_trade/serde", -] -serde_json = [ - "dep:serde_json", - "serde", - "nostr", - "radroots_event_codec/json", - "radroots_trade/serde_json", -] -nostr = [] -knowledge = [ - "serde", - "serde_json", - "radroots_event/knowledge", - "radroots_event_codec/manifests", -] -identity-models = [ - "radroots_identity/std", -] -signing = [ - "dep:nostr", +default = ["memory"] +memory = ["radroots_storage/memory"] +sqlite = ["dep:radroots_storage_sqlite"] +sync = ["dep:radroots_sync"] +nostr = [ + "sync", "dep:radroots_nostr", - "dep:radroots_protocol", - "nostr", - "radroots_nostr/std", - "radroots_protocol/std", - "radroots_signing/std", -] -transport-nostr-client = [ - "signing", - "std", - "serde_json", - "dep:radroots_transport_nostr", - "radroots_transport_nostr/client", -] -radrootsd-execution = [ - "std", - "serde_json", - "dep:futures", - "dep:radroots_protocol", - "dep:radroots_transport", "dep:radroots_transport_nostr", - "dep:reqwest", - "radroots_protocol/serde", - "radroots_protocol/std", - "radroots_transport/serde", - "radroots_transport_nostr/std", ] -signer-adapters = [ - "identity-models", - "local-signer", - "signing", - "std", +nip46 = [ + "nostr", "dep:radroots_nostr_connect", - "dep:radroots_nostr_signer", - "dep:tokio", - "radroots_nostr/events", ] -runtime = [ - "std", - "serde_json", - "dep:tokio", - "dep:base64", - "dep:hex", - "dep:radroots_event_store", - "dep:radroots_geocoder", - "dep:radroots_outbox", - "dep:radroots_protected_store", - "dep:radroots_protocol", - "dep:radroots_transport", - "dep:radroots_transport_nostr", - "dep:radroots_transport_reticulum", - "dep:radroots_runtime_paths", - "dep:radroots_secret_vault", - "dep:sha2", - "dep:sqlx", - "dep:uuid", - "radroots_signing/std", - "radroots_event_store/sqlite", - "radroots_event_store/runtime-tokio", - "radroots_outbox/sqlite", - "radroots_outbox/runtime-tokio", - "radroots_protected_store/std", - "radroots_protocol/std", - "radroots_secret_vault/std", - "radroots_transport/serde", - "radroots_transport_nostr/std", - "radroots_transport_nostr/storage", - "radroots_transport_nostr/runtime-tokio", -] -local-signer = [ - "runtime", - "signing", +local-signing = [ "dep:radroots_secrets", "radroots_nostr/signing", ] -transport-nostr-runtime = [ - "runtime", - "dep:radroots_nostr", - "radroots_transport_nostr/client", -] -local-runtime = [ - "std", - "serde", - "serde_json", - "runtime", - "signer-adapters", - "transport-nostr-runtime", - "transport-nostr-client", +radrootsd = [ + "sync", + "dep:reqwest", ] -local-runtime-radrootsd-execution = [ - "std", - "serde", - "serde_json", - "runtime", - "signer-adapters", - "radrootsd-execution", +geonames = ["dep:radroots_geonames"] +knowledge = [ + "radroots_event/knowledge", + "radroots_event_codec/knowledge", ] -test-fixture-geonames-asset = [ - "runtime", - "radroots_geocoder/test-fixture-geonames-asset", +native = ["sqlite", "sync", "local-signing"] +full = [ + "native", + "nostr", + "nip46", + "radrootsd", + "geonames", + "knowledge", ] [dependencies] -base64 = { workspace = true, optional = true } -futures = { workspace = true, optional = true } -radroots_event_store = { workspace = true, optional = true, default-features = false } +radroots_core = { workspace = true, default-features = false } radroots_event = { workspace = true, default-features = false } radroots_event_codec = { workspace = true, default-features = false } -radroots_geocoder = { workspace = true, optional = true } -radroots_outbox = { workspace = true, optional = true, default-features = false } -radroots_protocol = { workspace = true, optional = true, default-features = false } -radroots_protected_store = { workspace = true, optional = true, default-features = false } -radroots_transport = { workspace = true, optional = true, default-features = false } -radroots_transport_nostr = { workspace = true, optional = true, default-features = false } -radroots_transport_reticulum = { workspace = true, optional = true, default-features = false } -radroots_runtime_paths = { workspace = true, optional = true, default-features = false } -radroots_secret_vault = { workspace = true, optional = true, default-features = false } -radroots_secrets = { workspace = true, optional = true, default-features = false } +radroots_identity = { workspace = true, default-features = false } +radroots_protocol = { workspace = true, default-features = false } radroots_signing = { workspace = true, default-features = false } +radroots_storage = { workspace = true, default-features = false } radroots_trade = { workspace = true, default-features = false } -radroots_identity = { workspace = true, default-features = false } +radroots_transport = { workspace = true, default-features = false } + +radroots_geonames = { workspace = true, optional = true, default-features = false } radroots_nostr = { workspace = true, optional = true, default-features = false } -radroots_nostr_connect = { workspace = true, optional = true } -radroots_nostr_signer = { workspace = true, optional = true, default-features = false } +radroots_nostr_connect = { workspace = true, optional = true, default-features = false } +radroots_secrets = { workspace = true, optional = true, default-features = false } +radroots_storage_sqlite = { workspace = true, optional = true, default-features = false } +radroots_sync = { workspace = true, optional = true, default-features = false } +radroots_transport_nostr = { workspace = true, optional = true, default-features = false } + reqwest = { workspace = true, optional = true, default-features = false, features = [ "json", "rustls-tls", ] } -hex = { workspace = true, optional = true } -nostr = { workspace = true, optional = true } -serde = { workspace = true, optional = true, default-features = false, features = [ - "derive", - "alloc", -] } -serde_json = { workspace = true, optional = true, default-features = false, features = [ - "alloc", -] } -sha2 = { workspace = true, optional = true } -sqlx = { version = "0.9.0", optional = true, default-features = false, features = [ - "runtime-tokio", - "sqlite-bundled", -] } -tokio = { workspace = true, optional = true, features = ["macros", "rt", "sync", "time"] } -uuid = { workspace = true, optional = true } - -[[example]] -name = "sdk_v1_listing_prepare" -path = "examples/sdk_v1_listing_prepare.rs" -required-features = ["runtime"] - -[[example]] -name = "sdk_v1_knowledge_prepare" -path = "examples/sdk_v1_knowledge_prepare.rs" -required-features = ["knowledge"] - -[[example]] -name = "sdk_v1_local_enqueue_and_mock_sync" -path = "examples/sdk_v1_local_enqueue_and_mock_sync.rs" -required-features = ["runtime", "signer-adapters"] - -[[example]] -name = "sdk_v1_myc_nip46_signer_setup" -path = "examples/sdk_v1_myc_nip46_signer_setup.rs" -required-features = ["runtime", "signer-adapters"] -[[example]] -name = "runtime_local" -path = "examples/runtime_local.rs" -required-features = ["local-runtime"] - -[dev-dependencies] -futures = { workspace = true } -nostr = { workspace = true } -radroots_core = { workspace = true, default-features = false, features = [ - "std", -] } -radroots_signing = { workspace = true, default-features = false, features = ["std"] } -radroots_replica_store = { workspace = true, default-features = false, features = [ - "native", -] } -radroots_replica_schema = { workspace = true } -radroots_replica_sync = { workspace = true, features = ["std", "legacy-ingest"] } -radroots_sql_core = { workspace = true, features = ["native"] } -radroots_nostr = { workspace = true, default-features = false, features = [ - "std", - "events", - "signing", -] } -tempfile = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -tokio-tungstenite = "0.26.2" +[[test]] +name = "package_boundary" +path = "tests/package_boundary.rs" [lints] workspace = true diff --git a/crates/sdk/src/capability.rs b/crates/sdk/src/capability.rs @@ -0,0 +1 @@ +//! Client capability reporting. diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -0,0 +1 @@ +//! Client construction and lifecycle. diff --git a/crates/sdk/src/diagnostics.rs b/crates/sdk/src/diagnostics.rs @@ -0,0 +1 @@ +//! Redacted client diagnostics. diff --git a/crates/sdk/src/error.rs b/crates/sdk/src/error.rs @@ -1,1349 +1 @@ -#[cfg(feature = "runtime")] -use std::{fmt, path::PathBuf}; - -#[cfg(feature = "runtime")] -use crate::privacy::{PrivacyPreflightStatus, ProductSensitivityField}; -#[cfg(feature = "runtime")] -use crate::transport::ReticulumBehavior; -#[cfg(feature = "runtime")] -use radroots_event::trade::validation::OperationalListingValidationError; -#[cfg(feature = "runtime")] -use serde_json::{Value, json}; - -#[cfg(feature = "runtime")] -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum RadrootsSdkErrorClass { - Authorization, - Clock, - Configuration, - Request, - Storage, - Transport, - Unsupported, -} - -#[cfg(feature = "runtime")] -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum RadrootsSdkRecoveryAction { - InspectLocalStores, - InspectGeoNamesAsset, - RetryOperationWithSameIdempotencyKey, - ConfigureTransportTargets, - ConfigureGeoNamesCache, - ConfigureSigner, - FixRequest, - SelectAuthorizedActor, - CompleteSignerAuthentication, - RetryAfterTransportFailure, - RetryGeoNamesDownload, - EnableRequiredFeature, -} - -#[cfg(feature = "runtime")] -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum RadrootsSdkGeoNamesErrorKind { - Configuration, - Download, - Cache, - Integrity, - Schema, - Lookup, -} - -#[cfg(feature = "runtime")] -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum RadrootsSdkListingValidationErrorKind { - InvalidKind, - InvalidProfile, - MissingListingId, - ListingEventNotFound, - ListingEventFetchFailed, - ParseError, - InvalidSeller, - MissingFarmProfile, - MissingFarmRecord, - MissingTitle, - MissingDescription, - MissingProductType, - MissingBins, - MissingPrimaryBin, - InvalidBin, - MissingPrice, - InvalidPrice, - MissingInventory, - InvalidInventory, - MissingAvailability, - MissingLocation, - MissingLocationLocality, - MissingLocationGeohash, - InvalidLocationGeohash, - MissingDeliveryMethod, -} - -#[cfg(feature = "runtime")] -impl From<&OperationalListingValidationError> for RadrootsSdkListingValidationErrorKind { - fn from(error: &OperationalListingValidationError) -> Self { - match error { - OperationalListingValidationError::InvalidKind { .. } => Self::InvalidKind, - OperationalListingValidationError::InvalidProfile => Self::InvalidProfile, - OperationalListingValidationError::MissingListingId => Self::MissingListingId, - OperationalListingValidationError::ListingEventNotFound { .. } => { - Self::ListingEventNotFound - } - OperationalListingValidationError::ListingEventFetchFailed { .. } => { - Self::ListingEventFetchFailed - } - OperationalListingValidationError::ParseError { .. } => Self::ParseError, - OperationalListingValidationError::InvalidSeller => Self::InvalidSeller, - OperationalListingValidationError::MissingFarmProfile => Self::MissingFarmProfile, - OperationalListingValidationError::MissingFarmRecord => Self::MissingFarmRecord, - OperationalListingValidationError::MissingTitle => Self::MissingTitle, - OperationalListingValidationError::MissingDescription => Self::MissingDescription, - OperationalListingValidationError::MissingProductType => Self::MissingProductType, - OperationalListingValidationError::MissingBins => Self::MissingBins, - OperationalListingValidationError::MissingPrimaryBin => Self::MissingPrimaryBin, - OperationalListingValidationError::InvalidBin => Self::InvalidBin, - OperationalListingValidationError::MissingPrice => Self::MissingPrice, - OperationalListingValidationError::InvalidPrice => Self::InvalidPrice, - OperationalListingValidationError::MissingInventory => Self::MissingInventory, - OperationalListingValidationError::InvalidInventory => Self::InvalidInventory, - OperationalListingValidationError::MissingAvailability => Self::MissingAvailability, - OperationalListingValidationError::MissingLocation => Self::MissingLocation, - OperationalListingValidationError::MissingLocationLocality => { - Self::MissingLocationLocality - } - OperationalListingValidationError::MissingLocationGeohash => { - Self::MissingLocationGeohash - } - OperationalListingValidationError::InvalidLocationGeohash => { - Self::InvalidLocationGeohash - } - OperationalListingValidationError::MissingDeliveryMethod => Self::MissingDeliveryMethod, - } - } -} - -#[cfg(feature = "runtime")] -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum RadrootsSdkTradeErrorKind { - InvalidEnvelope, - InvalidCommandBody, - PrivateArtifactMissing, - PrivateArtifactCommitmentMismatch, - PrivateArtifactAcknowledgementMissing, - TradeNotFound, - QueryLimitInvalid, - CursorInvalid, -} - -#[cfg(feature = "runtime")] -impl RadrootsSdkTradeErrorKind { - pub fn code(self) -> &'static str { - match self { - Self::InvalidEnvelope => "trade_invalid_envelope", - Self::InvalidCommandBody => "trade_invalid_command_body", - Self::PrivateArtifactMissing => "trade_private_artifact_missing", - Self::PrivateArtifactCommitmentMismatch => "trade_private_artifact_commitment_mismatch", - Self::PrivateArtifactAcknowledgementMissing => { - "trade_private_artifact_acknowledgement_missing" - } - Self::TradeNotFound => "trade_not_found", - Self::QueryLimitInvalid => "trade_query_limit_invalid", - Self::CursorInvalid => "trade_cursor_invalid", - } - } -} - -#[cfg(feature = "runtime")] -const RECOVERY_INSPECT_LOCAL_STORES: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::InspectLocalStores]; -#[cfg(feature = "runtime")] -const RECOVERY_INSPECT_GEONAMES_ASSET: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::InspectGeoNamesAsset]; -#[cfg(feature = "runtime")] -const RECOVERY_RETRY_OPERATION_WITH_SAME_IDEMPOTENCY_KEY: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey]; -#[cfg(feature = "runtime")] -const RECOVERY_CONFIGURE_TRANSPORT_TARGETS: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::ConfigureTransportTargets]; -#[cfg(feature = "runtime")] -const RECOVERY_CONFIGURE_GEONAMES_CACHE: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::ConfigureGeoNamesCache]; -#[cfg(feature = "runtime")] -const RECOVERY_CONFIGURE_SIGNER: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::ConfigureSigner]; -#[cfg(feature = "runtime")] -const RECOVERY_FIX_REQUEST: &[RadrootsSdkRecoveryAction] = &[RadrootsSdkRecoveryAction::FixRequest]; -#[cfg(feature = "runtime")] -const RECOVERY_SELECT_AUTHORIZED_ACTOR: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::SelectAuthorizedActor]; -#[cfg(feature = "runtime")] -const RECOVERY_COMPLETE_SIGNER_AUTHENTICATION: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::CompleteSignerAuthentication]; -#[cfg(feature = "runtime")] -const RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::RetryAfterTransportFailure]; -#[cfg(feature = "runtime")] -const RECOVERY_RETRY_GEONAMES_DOWNLOAD: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::RetryGeoNamesDownload]; -#[cfg(feature = "runtime")] -const RECOVERY_ENABLE_REQUIRED_FEATURE: &[RadrootsSdkRecoveryAction] = - &[RadrootsSdkRecoveryAction::EnableRequiredFeature]; - -#[cfg(feature = "runtime")] -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -pub struct RadrootsSdkErrorCatalogEntry { - pub code: &'static str, - pub class: RadrootsSdkErrorClass, - pub retryable: bool, - pub recovery_actions: &'static [RadrootsSdkRecoveryAction], -} - -#[cfg(feature = "runtime")] -impl RadrootsSdkErrorCatalogEntry { - const fn new( - code: &'static str, - class: RadrootsSdkErrorClass, - retryable: bool, - recovery_actions: &'static [RadrootsSdkRecoveryAction], - ) -> Self { - Self { - code, - class, - retryable, - recovery_actions, - } - } -} - -#[cfg(feature = "runtime")] -pub const RADROOTS_SDK_ERROR_CATALOG: &[RadrootsSdkErrorCatalogEntry] = &[ - RadrootsSdkErrorCatalogEntry::new( - "io", - RadrootsSdkErrorClass::Storage, - true, - RECOVERY_INSPECT_LOCAL_STORES, - ), - RadrootsSdkErrorCatalogEntry::new( - "clock_before_unix_epoch", - RadrootsSdkErrorClass::Clock, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "timestamp_out_of_range", - RadrootsSdkErrorClass::Clock, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "unauthorized_actor", - RadrootsSdkErrorClass::Authorization, - false, - RECOVERY_SELECT_AUTHORIZED_ACTOR, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_pubkey_mismatch", - RadrootsSdkErrorClass::Authorization, - false, - RECOVERY_SELECT_AUTHORIZED_ACTOR, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_unavailable", - RadrootsSdkErrorClass::Configuration, - false, - RECOVERY_CONFIGURE_SIGNER, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_request_rejected", - RadrootsSdkErrorClass::Authorization, - false, - RECOVERY_SELECT_AUTHORIZED_ACTOR, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_request_timed_out", - RadrootsSdkErrorClass::Transport, - true, - RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_auth_challenge_pending", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_COMPLETE_SIGNER_AUTHENTICATION, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_transport", - RadrootsSdkErrorClass::Transport, - true, - RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_protocol", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "signer_returned_event_drift", - RadrootsSdkErrorClass::Authorization, - false, - RECOVERY_CONFIGURE_SIGNER, - ), - RadrootsSdkErrorCatalogEntry::new( - "empty_transport_targets", - RadrootsSdkErrorClass::Configuration, - false, - RECOVERY_CONFIGURE_TRANSPORT_TARGETS, - ), - RadrootsSdkErrorCatalogEntry::new( - "transport_target_limit_exceeded", - RadrootsSdkErrorClass::Configuration, - false, - RECOVERY_CONFIGURE_TRANSPORT_TARGETS, - ), - RadrootsSdkErrorCatalogEntry::new( - "invalid_relay_url", - RadrootsSdkErrorClass::Configuration, - false, - RECOVERY_CONFIGURE_TRANSPORT_TARGETS, - ), - RadrootsSdkErrorCatalogEntry::new( - "idempotency_conflict", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_RETRY_OPERATION_WITH_SAME_IDEMPOTENCY_KEY, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_invalid_envelope", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_invalid_command_body", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_private_artifact_missing", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_private_artifact_commitment_mismatch", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_private_artifact_acknowledgement_missing", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_not_found", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_query_limit_invalid", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "trade_cursor_invalid", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "privacy_preflight", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "product_sync_unsupported", - RadrootsSdkErrorClass::Unsupported, - false, - RECOVERY_ENABLE_REQUIRED_FEATURE, - ), - RadrootsSdkErrorCatalogEntry::new( - "reticulum_transport_unavailable", - RadrootsSdkErrorClass::Unsupported, - false, - RECOVERY_CONFIGURE_TRANSPORT_TARGETS, - ), - RadrootsSdkErrorCatalogEntry::new( - "reticulum_transport_deferred", - RadrootsSdkErrorClass::Unsupported, - false, - RECOVERY_CONFIGURE_TRANSPORT_TARGETS, - ), - RadrootsSdkErrorCatalogEntry::new( - "product_sync_transport_setup_failure", - RadrootsSdkErrorClass::Transport, - true, - RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE, - ), - RadrootsSdkErrorCatalogEntry::new( - "authority", - RadrootsSdkErrorClass::Authorization, - false, - RECOVERY_SELECT_AUTHORIZED_ACTOR, - ), - RadrootsSdkErrorCatalogEntry::new( - "event_store", - RadrootsSdkErrorClass::Storage, - true, - RECOVERY_INSPECT_LOCAL_STORES, - ), - RadrootsSdkErrorCatalogEntry::new( - "invalid_request", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "unsupported_profile_schema", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_INSPECT_LOCAL_STORES, - ), - RadrootsSdkErrorCatalogEntry::new( - "listing_edit", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "listing_validation", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "listing_mutation", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "outbox", - RadrootsSdkErrorClass::Storage, - true, - RECOVERY_INSPECT_LOCAL_STORES, - ), - RadrootsSdkErrorCatalogEntry::new( - "private_store", - RadrootsSdkErrorClass::Storage, - true, - RECOVERY_INSPECT_LOCAL_STORES, - ), - RadrootsSdkErrorCatalogEntry::new( - "studio_store", - RadrootsSdkErrorClass::Storage, - true, - RECOVERY_INSPECT_LOCAL_STORES, - ), - RadrootsSdkErrorCatalogEntry::new( - "geonames_configuration", - RadrootsSdkErrorClass::Configuration, - false, - RECOVERY_CONFIGURE_GEONAMES_CACHE, - ), - RadrootsSdkErrorCatalogEntry::new( - "geonames_download", - RadrootsSdkErrorClass::Transport, - true, - RECOVERY_RETRY_GEONAMES_DOWNLOAD, - ), - RadrootsSdkErrorCatalogEntry::new( - "geonames_cache", - RadrootsSdkErrorClass::Storage, - true, - RECOVERY_INSPECT_GEONAMES_ASSET, - ), - RadrootsSdkErrorCatalogEntry::new( - "geonames_integrity", - RadrootsSdkErrorClass::Storage, - false, - RECOVERY_INSPECT_GEONAMES_ASSET, - ), - RadrootsSdkErrorCatalogEntry::new( - "geonames_schema", - RadrootsSdkErrorClass::Storage, - false, - RECOVERY_INSPECT_GEONAMES_ASSET, - ), - RadrootsSdkErrorCatalogEntry::new( - "geonames_lookup", - RadrootsSdkErrorClass::Request, - false, - RECOVERY_FIX_REQUEST, - ), - RadrootsSdkErrorCatalogEntry::new( - "transport", - RadrootsSdkErrorClass::Transport, - true, - RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE, - ), - RadrootsSdkErrorCatalogEntry::new( - "projection", - RadrootsSdkErrorClass::Storage, - true, - RECOVERY_INSPECT_LOCAL_STORES, - ), -]; - -#[cfg(feature = "runtime")] -pub fn radroots_sdk_error_catalog() -> &'static [RadrootsSdkErrorCatalogEntry] { - RADROOTS_SDK_ERROR_CATALOG -} - -#[cfg(feature = "runtime")] -#[derive(Debug)] -#[non_exhaustive] -pub enum RadrootsSdkError { - Io { - path: PathBuf, - message: String, - }, - ClockBeforeUnixEpoch, - TimestampOutOfRange { - value: u64, - }, - UnauthorizedActor { - operation: String, - reason: String, - }, - SignerPubkeyMismatch { - operation: String, - expected_pubkey_prefix: String, - signer_pubkey_prefix: String, - }, - SignerUnavailable { - mode: String, - reason: String, - }, - SignerRequestRejected { - mode: String, - reason: String, - }, - SignerRequestTimedOut { - mode: String, - }, - SignerAuthChallengePending { - mode: String, - auth_url: Option<String>, - }, - SignerTransport { - mode: String, - reason: String, - }, - SignerProtocol { - mode: String, - reason: String, - }, - SignerReturnedEventDrift { - operation: String, - reason: String, - }, - EmptyTransportTargets { - operation: String, - }, - TransportTargetLimitExceeded { - max: usize, - actual: usize, - }, - InvalidRelayUrl { - url: String, - reason: String, - }, - IdempotencyConflict { - operation_kind: String, - expected_pubkey_prefix: String, - existing_digest_prefix: String, - new_digest_prefix: String, - }, - Trade { - kind: RadrootsSdkTradeErrorKind, - operation: String, - message: String, - }, - PrivacyPreflight { - operation: String, - status: PrivacyPreflightStatus, - fields: Vec<ProductSensitivityField>, - }, - ProductSyncUnsupported { - operation: &'static str, - required_feature: &'static str, - }, - ReticulumTransportUnavailable { - operation: String, - endpoint_uri: String, - behavior: ReticulumBehavior, - }, - ProductSyncTransportSetupFailure { - message: String, - }, - Authority { - message: String, - }, - EventStore { - message: String, - }, - InvalidRequest { - message: String, - }, - UnsupportedProfileSchema { - path: PathBuf, - message: String, - }, - ListingEdit { - message: String, - }, - ListingValidation { - kind: RadrootsSdkListingValidationErrorKind, - message: String, - }, - ListingMutation { - message: String, - }, - Outbox { - message: String, - }, - PrivateStore { - message: String, - }, - StudioStore { - message: String, - }, - GeoNames { - kind: RadrootsSdkGeoNamesErrorKind, - message: String, - }, - Transport { - message: String, - }, - Projection { - message: String, - }, -} - -#[cfg(feature = "runtime")] -impl RadrootsSdkError { - pub fn code(&self) -> &'static str { - match self { - Self::Io { .. } => "io", - Self::ClockBeforeUnixEpoch => "clock_before_unix_epoch", - Self::TimestampOutOfRange { .. } => "timestamp_out_of_range", - Self::UnauthorizedActor { .. } => "unauthorized_actor", - Self::SignerPubkeyMismatch { .. } => "signer_pubkey_mismatch", - Self::SignerUnavailable { .. } => "signer_unavailable", - Self::SignerRequestRejected { .. } => "signer_request_rejected", - Self::SignerRequestTimedOut { .. } => "signer_request_timed_out", - Self::SignerAuthChallengePending { .. } => "signer_auth_challenge_pending", - Self::SignerTransport { .. } => "signer_transport", - Self::SignerProtocol { .. } => "signer_protocol", - Self::SignerReturnedEventDrift { .. } => "signer_returned_event_drift", - Self::EmptyTransportTargets { .. } => "empty_transport_targets", - Self::TransportTargetLimitExceeded { .. } => "transport_target_limit_exceeded", - Self::InvalidRelayUrl { .. } => "invalid_relay_url", - Self::IdempotencyConflict { .. } => "idempotency_conflict", - Self::Trade { kind, .. } => kind.code(), - Self::PrivacyPreflight { .. } => "privacy_preflight", - Self::ProductSyncUnsupported { .. } => "product_sync_unsupported", - Self::ReticulumTransportUnavailable { behavior, .. } => match behavior { - ReticulumBehavior::RejectDeliveryAttempts => "reticulum_transport_unavailable", - ReticulumBehavior::DeferDeliveryPlans => "reticulum_transport_deferred", - }, - Self::ProductSyncTransportSetupFailure { .. } => "product_sync_transport_setup_failure", - Self::Authority { .. } => "authority", - Self::EventStore { .. } => "event_store", - Self::InvalidRequest { .. } => "invalid_request", - Self::UnsupportedProfileSchema { .. } => "unsupported_profile_schema", - Self::ListingEdit { .. } => "listing_edit", - Self::ListingValidation { .. } => "listing_validation", - Self::ListingMutation { .. } => "listing_mutation", - Self::Outbox { .. } => "outbox", - Self::PrivateStore { .. } => "private_store", - Self::StudioStore { .. } => "studio_store", - Self::GeoNames { kind, .. } => match kind { - RadrootsSdkGeoNamesErrorKind::Configuration => "geonames_configuration", - RadrootsSdkGeoNamesErrorKind::Download => "geonames_download", - RadrootsSdkGeoNamesErrorKind::Cache => "geonames_cache", - RadrootsSdkGeoNamesErrorKind::Integrity => "geonames_integrity", - RadrootsSdkGeoNamesErrorKind::Schema => "geonames_schema", - RadrootsSdkGeoNamesErrorKind::Lookup => "geonames_lookup", - }, - Self::Transport { .. } => "transport", - Self::Projection { .. } => "projection", - } - } - - pub fn class(&self) -> RadrootsSdkErrorClass { - match self { - Self::Io { .. } - | Self::EventStore { .. } - | Self::Outbox { .. } - | Self::PrivateStore { .. } - | Self::StudioStore { .. } - | Self::Projection { .. } => RadrootsSdkErrorClass::Storage, - Self::GeoNames { kind, .. } => match kind { - RadrootsSdkGeoNamesErrorKind::Configuration => RadrootsSdkErrorClass::Configuration, - RadrootsSdkGeoNamesErrorKind::Download => RadrootsSdkErrorClass::Transport, - RadrootsSdkGeoNamesErrorKind::Cache - | RadrootsSdkGeoNamesErrorKind::Integrity - | RadrootsSdkGeoNamesErrorKind::Schema => RadrootsSdkErrorClass::Storage, - RadrootsSdkGeoNamesErrorKind::Lookup => RadrootsSdkErrorClass::Request, - }, - Self::ClockBeforeUnixEpoch | Self::TimestampOutOfRange { .. } => { - RadrootsSdkErrorClass::Clock - } - Self::UnauthorizedActor { .. } - | Self::SignerPubkeyMismatch { .. } - | Self::SignerRequestRejected { .. } - | Self::SignerReturnedEventDrift { .. } - | Self::Authority { .. } => RadrootsSdkErrorClass::Authorization, - Self::SignerUnavailable { .. } => RadrootsSdkErrorClass::Configuration, - Self::EmptyTransportTargets { .. } - | Self::TransportTargetLimitExceeded { .. } - | Self::InvalidRelayUrl { .. } => RadrootsSdkErrorClass::Configuration, - Self::IdempotencyConflict { .. } - | Self::Trade { .. } - | Self::PrivacyPreflight { .. } - | Self::SignerProtocol { .. } - | Self::SignerAuthChallengePending { .. } - | Self::InvalidRequest { .. } - | Self::UnsupportedProfileSchema { .. } - | Self::ListingEdit { .. } - | Self::ListingValidation { .. } - | Self::ListingMutation { .. } => RadrootsSdkErrorClass::Request, - Self::ProductSyncUnsupported { .. } | Self::ReticulumTransportUnavailable { .. } => { - RadrootsSdkErrorClass::Unsupported - } - Self::ProductSyncTransportSetupFailure { .. } - | Self::Transport { .. } - | Self::SignerRequestTimedOut { .. } - | Self::SignerTransport { .. } => RadrootsSdkErrorClass::Transport, - } - } - - pub fn retryable(&self) -> bool { - matches!( - self, - Self::Io { .. } - | Self::ProductSyncTransportSetupFailure { .. } - | Self::EventStore { .. } - | Self::Outbox { .. } - | Self::PrivateStore { .. } - | Self::StudioStore { .. } - | Self::GeoNames { - kind: RadrootsSdkGeoNamesErrorKind::Cache - | RadrootsSdkGeoNamesErrorKind::Download, - .. - } - | Self::Transport { .. } - | Self::SignerRequestTimedOut { .. } - | Self::SignerTransport { .. } - | Self::Projection { .. } - ) - } - - pub fn recovery_actions(&self) -> Vec<RadrootsSdkRecoveryAction> { - match self { - Self::Io { .. } - | Self::EventStore { .. } - | Self::Outbox { .. } - | Self::PrivateStore { .. } - | Self::StudioStore { .. } - | Self::Projection { .. } => vec![RadrootsSdkRecoveryAction::InspectLocalStores], - Self::GeoNames { kind, .. } => match kind { - RadrootsSdkGeoNamesErrorKind::Configuration => { - vec![RadrootsSdkRecoveryAction::ConfigureGeoNamesCache] - } - RadrootsSdkGeoNamesErrorKind::Download => { - vec![RadrootsSdkRecoveryAction::RetryGeoNamesDownload] - } - RadrootsSdkGeoNamesErrorKind::Cache - | RadrootsSdkGeoNamesErrorKind::Integrity - | RadrootsSdkGeoNamesErrorKind::Schema => { - vec![RadrootsSdkRecoveryAction::InspectGeoNamesAsset] - } - RadrootsSdkGeoNamesErrorKind::Lookup => vec![RadrootsSdkRecoveryAction::FixRequest], - }, - Self::UnauthorizedActor { .. } - | Self::SignerPubkeyMismatch { .. } - | Self::SignerRequestRejected { .. } - | Self::Authority { .. } => vec![RadrootsSdkRecoveryAction::SelectAuthorizedActor], - Self::SignerUnavailable { .. } | Self::SignerReturnedEventDrift { .. } => { - vec![RadrootsSdkRecoveryAction::ConfigureSigner] - } - Self::EmptyTransportTargets { .. } - | Self::TransportTargetLimitExceeded { .. } - | Self::InvalidRelayUrl { .. } => { - vec![RadrootsSdkRecoveryAction::ConfigureTransportTargets] - } - Self::IdempotencyConflict { .. } => { - vec![RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey] - } - Self::Trade { .. } => vec![RadrootsSdkRecoveryAction::FixRequest], - Self::PrivacyPreflight { .. } => vec![RadrootsSdkRecoveryAction::FixRequest], - Self::UnsupportedProfileSchema { .. } => { - vec![RadrootsSdkRecoveryAction::InspectLocalStores] - } - Self::ProductSyncUnsupported { .. } => { - vec![RadrootsSdkRecoveryAction::EnableRequiredFeature] - } - Self::ReticulumTransportUnavailable { .. } => { - vec![RadrootsSdkRecoveryAction::ConfigureTransportTargets] - } - Self::ProductSyncTransportSetupFailure { .. } | Self::Transport { .. } => { - vec![RadrootsSdkRecoveryAction::RetryAfterTransportFailure] - } - Self::SignerRequestTimedOut { .. } | Self::SignerTransport { .. } => { - vec![RadrootsSdkRecoveryAction::RetryAfterTransportFailure] - } - Self::SignerAuthChallengePending { .. } => { - vec![RadrootsSdkRecoveryAction::CompleteSignerAuthentication] - } - Self::ClockBeforeUnixEpoch - | Self::TimestampOutOfRange { .. } - | Self::SignerProtocol { .. } - | Self::InvalidRequest { .. } - | Self::ListingEdit { .. } - | Self::ListingValidation { .. } - | Self::ListingMutation { .. } => vec![RadrootsSdkRecoveryAction::FixRequest], - } - } - - pub fn detail_json(&self) -> Value { - let detail = match self { - Self::Io { path, message } => { - json!({ "path": path.display().to_string(), "message": message }) - } - Self::ClockBeforeUnixEpoch => json!({}), - Self::TimestampOutOfRange { value } => json!({ "value": value }), - Self::UnauthorizedActor { operation, reason } => { - json!({ "operation": operation, "reason": reason }) - } - Self::SignerPubkeyMismatch { - operation, - expected_pubkey_prefix, - signer_pubkey_prefix, - } => json!({ - "operation": operation, - "expected_pubkey_prefix": expected_pubkey_prefix, - "signer_pubkey_prefix": signer_pubkey_prefix - }), - Self::SignerUnavailable { mode, reason } - | Self::SignerRequestRejected { mode, reason } - | Self::SignerTransport { mode, reason } - | Self::SignerProtocol { mode, reason } => { - json!({ "mode": mode, "reason": reason }) - } - Self::SignerRequestTimedOut { mode } => json!({ "mode": mode }), - Self::SignerAuthChallengePending { mode, auth_url } => { - json!({ "mode": mode, "auth_url": auth_url }) - } - Self::SignerReturnedEventDrift { operation, reason } => { - json!({ "operation": operation, "reason": reason }) - } - Self::EmptyTransportTargets { operation } => json!({ "operation": operation }), - Self::TransportTargetLimitExceeded { max, actual } => { - json!({ "max": max, "actual": actual }) - } - Self::InvalidRelayUrl { url, reason } => json!({ "url": url, "reason": reason }), - Self::IdempotencyConflict { - operation_kind, - expected_pubkey_prefix, - existing_digest_prefix, - new_digest_prefix, - } => json!({ - "operation_kind": operation_kind, - "expected_pubkey_prefix": expected_pubkey_prefix, - "existing_digest_prefix": existing_digest_prefix, - "new_digest_prefix": new_digest_prefix - }), - Self::Trade { - kind, - operation, - message, - } => json!({ - "kind": kind, - "operation": operation, - "message": message - }), - Self::PrivacyPreflight { - operation, - status, - fields, - } => json!({ - "operation": operation, - "status": status, - "fields": fields - }), - Self::ProductSyncUnsupported { - operation, - required_feature, - } => json!({ "operation": operation, "required_feature": required_feature }), - Self::ReticulumTransportUnavailable { - operation, - endpoint_uri, - behavior, - } => json!({ - "operation": operation, - "endpoint_uri": endpoint_uri, - "behavior": behavior.as_str() - }), - Self::ListingValidation { kind, message } => { - json!({ "kind": kind, "message": message }) - } - Self::ProductSyncTransportSetupFailure { message } - | Self::Authority { message } - | Self::EventStore { message } - | Self::InvalidRequest { message } - | Self::ListingEdit { message } - | Self::ListingMutation { message } - | Self::Outbox { message } - | Self::PrivateStore { message } - | Self::StudioStore { message } - | Self::Transport { message } - | Self::Projection { message } => json!({ "message": message }), - Self::UnsupportedProfileSchema { path, message } => { - json!({ "path": path.display().to_string(), "message": message }) - } - Self::GeoNames { kind, message } => json!({ "kind": kind, "message": message }), - }; - json!({ - "code": self.code(), - "class": self.class(), - "retryable": self.retryable(), - "message": self.to_string(), - "recovery_actions": self.recovery_actions(), - "detail": detail - }) - } - - pub(crate) fn empty_transport_targets(operation: impl Into<String>) -> Self { - Self::EmptyTransportTargets { - operation: operation.into(), - } - } - - pub(crate) fn transport_target_limit_exceeded(max: usize, actual: usize) -> Self { - Self::TransportTargetLimitExceeded { max, actual } - } - - pub(crate) fn invalid_relay_url(url: impl Into<String>, reason: impl Into<String>) -> Self { - Self::InvalidRelayUrl { - url: redacted_relay_url(url.into()), - reason: reason.into(), - } - } - - pub(crate) fn missing_geonames_config() -> Self { - Self::GeoNames { - kind: RadrootsSdkGeoNamesErrorKind::Configuration, - message: "GeoNames cache root is not configured".to_owned(), - } - } -} - -#[cfg(feature = "runtime")] -impl fmt::Display for RadrootsSdkError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io { path, message } => { - write!(f, "sdk storage path `{}` failed: {message}", path.display()) - } - Self::ClockBeforeUnixEpoch => f.write_str("sdk clock is before the Unix epoch"), - Self::TimestampOutOfRange { value } => { - write!( - f, - "sdk timestamp {value} exceeds Nostr u32 created_at range" - ) - } - Self::UnauthorizedActor { operation, reason } => { - write!(f, "sdk unauthorized actor for {operation}: {reason}") - } - Self::SignerPubkeyMismatch { - operation, - expected_pubkey_prefix, - signer_pubkey_prefix, - } => write!( - f, - "sdk signer pubkey mismatch for {operation}: expected_pubkey_prefix={expected_pubkey_prefix}, signer_pubkey_prefix={signer_pubkey_prefix}" - ), - Self::SignerUnavailable { mode, reason } => { - write!(f, "sdk {mode} signer unavailable: {reason}") - } - Self::SignerRequestRejected { mode, reason } => { - write!(f, "sdk {mode} signer rejected request: {reason}") - } - Self::SignerRequestTimedOut { mode } => { - write!(f, "sdk {mode} signer request timed out") - } - Self::SignerAuthChallengePending { mode, auth_url } => match auth_url { - Some(auth_url) => { - write!(f, "sdk {mode} signer requires authentication at {auth_url}") - } - None => write!(f, "sdk {mode} signer requires authentication"), - }, - Self::SignerTransport { mode, reason } => { - write!(f, "sdk {mode} signer transport error: {reason}") - } - Self::SignerProtocol { mode, reason } => { - write!(f, "sdk {mode} signer protocol error: {reason}") - } - Self::SignerReturnedEventDrift { operation, reason } => { - write!( - f, - "sdk signer returned event drift for {operation}: {reason}" - ) - } - Self::EmptyTransportTargets { operation } => { - write!(f, "sdk empty transport targets for {operation}") - } - Self::TransportTargetLimitExceeded { max, actual } => { - write!( - f, - "sdk transport target limit exceeded: max={max}, actual={actual}" - ) - } - Self::InvalidRelayUrl { url, reason } => { - write!(f, "sdk invalid relay URL `{url}`: {reason}") - } - Self::IdempotencyConflict { - operation_kind, - expected_pubkey_prefix, - existing_digest_prefix, - new_digest_prefix, - } => write!( - f, - "sdk idempotency conflict for {operation_kind}: expected_pubkey_prefix={expected_pubkey_prefix}, existing_digest_prefix={existing_digest_prefix}, new_digest_prefix={new_digest_prefix}" - ), - Self::Trade { - kind, - operation, - message, - } => write!(f, "sdk trade error for {operation} ({kind:?}): {message}"), - Self::PrivacyPreflight { - operation, - status, - fields, - } => write!( - f, - "sdk privacy preflight failed for {operation}: status={status:?}, field_count={}", - fields.len() - ), - Self::ProductSyncUnsupported { - operation, - required_feature, - } => write!( - f, - "sdk product sync operation {operation} requires feature `{required_feature}`" - ), - Self::ReticulumTransportUnavailable { - operation, - endpoint_uri, - behavior, - } => write!( - f, - "sdk product sync operation {operation} cannot deliver through Reticulum endpoint `{endpoint_uri}` with behavior `{}`", - behavior.as_str() - ), - Self::ProductSyncTransportSetupFailure { message } => { - write!(f, "sdk product sync transport setup failed: {message}") - } - Self::Authority { message } => write!(f, "sdk authority error: {message}"), - Self::EventStore { message } => write!(f, "sdk event store error: {message}"), - Self::InvalidRequest { message } => write!(f, "sdk invalid request: {message}"), - Self::UnsupportedProfileSchema { path, message } => write!( - f, - "sdk unsupported profile schema at `{}`: {message}", - path.display() - ), - Self::ListingEdit { message } => write!(f, "sdk listing edit error: {message}"), - Self::ListingValidation { kind, message } => { - write!(f, "sdk listing validation error ({kind:?}): {message}") - } - Self::ListingMutation { message } => { - write!(f, "sdk listing mutation error: {message}") - } - Self::Outbox { message } => write!(f, "sdk outbox error: {message}"), - Self::PrivateStore { message } => write!(f, "sdk private store error: {message}"), - Self::StudioStore { message } => write!(f, "sdk studio store error: {message}"), - Self::GeoNames { kind, message } => { - write!(f, "sdk GeoNames {kind:?} error: {message}") - } - Self::Transport { message } => { - write!(f, "sdk transport error: {message}") - } - Self::Projection { message } => write!(f, "sdk projection error: {message}"), - } - } -} - -#[cfg(feature = "runtime")] -impl std::error::Error for RadrootsSdkError {} - -#[cfg(feature = "runtime")] -impl From<radroots_signing::Error> for RadrootsSdkError { - fn from(error: radroots_signing::Error) -> Self { - use radroots_signing::error::Kind; - - match error.kind() { - Kind::AuthorizationDenied => Self::UnauthorizedActor { - operation: "event signing".to_owned(), - reason: "actor or signer is not authorized for the frozen draft".to_owned(), - }, - Kind::SignerCapabilityMissing => Self::SignerUnavailable { - mode: "configured".to_owned(), - reason: error.to_string(), - }, - Kind::SignerUnavailable => Self::SignerUnavailable { - mode: "configured".to_owned(), - reason: error.to_string(), - }, - Kind::SignerRejected => Self::SignerRequestRejected { - mode: "configured".to_owned(), - reason: error.to_string(), - }, - Kind::SignerTimeout | Kind::DeadlineExceeded => Self::SignerRequestTimedOut { - mode: "configured".to_owned(), - }, - Kind::SignerCancelled => Self::SignerRequestRejected { - mode: "configured".to_owned(), - reason: error.to_string(), - }, - Kind::SignerOutputInvalid => Self::SignerReturnedEventDrift { - operation: "event signing".to_owned(), - reason: error.to_string(), - }, - Kind::InvalidArgument | Kind::InternalError => Self::Authority { - message: error.to_string(), - }, - _ => Self::Authority { - message: error.to_string(), - }, - } - } -} - -#[cfg(feature = "runtime")] -impl From<radroots_event_store::RadrootsEventStoreError> for RadrootsSdkError { - fn from(error: radroots_event_store::RadrootsEventStoreError) -> Self { - Self::EventStore { - message: error.to_string(), - } - } -} - -#[cfg(feature = "runtime")] -impl From<radroots_geocoder::GeocoderError> for RadrootsSdkError { - fn from(error: radroots_geocoder::GeocoderError) -> Self { - let kind = match &error { - radroots_geocoder::GeocoderError::InvalidAssetUrl { .. } - | radroots_geocoder::GeocoderError::InvalidAssetHost { .. } => { - RadrootsSdkGeoNamesErrorKind::Configuration - } - radroots_geocoder::GeocoderError::AssetDownload { .. } => { - RadrootsSdkGeoNamesErrorKind::Download - } - radroots_geocoder::GeocoderError::Io(_) - | radroots_geocoder::GeocoderError::Sqlite(_) - | radroots_geocoder::GeocoderError::SqliteConnectionLockUnavailable - | radroots_geocoder::GeocoderError::AssetLockUnavailable { .. } => { - RadrootsSdkGeoNamesErrorKind::Cache - } - radroots_geocoder::GeocoderError::InvalidAssetSchema { .. } => { - RadrootsSdkGeoNamesErrorKind::Schema - } - radroots_geocoder::GeocoderError::InvalidAssetLength { .. } - | radroots_geocoder::GeocoderError::InvalidAssetSha256 { .. } - | radroots_geocoder::GeocoderError::InvalidAssetSqlite { .. } - | radroots_geocoder::GeocoderError::InvalidAssetIntegrity { .. } => { - RadrootsSdkGeoNamesErrorKind::Integrity - } - radroots_geocoder::GeocoderError::CountryCenterNotFound { .. } => { - RadrootsSdkGeoNamesErrorKind::Lookup - } - }; - Self::GeoNames { - kind, - message: error.to_string(), - } - } -} - -#[cfg(feature = "runtime")] -impl From<radroots_trade::operational_listing::RadrootsOperationalListingEditError> - for RadrootsSdkError -{ - fn from( - error: radroots_trade::operational_listing::RadrootsOperationalListingEditError, - ) -> Self { - match error { - radroots_trade::operational_listing::RadrootsOperationalListingEditError::InvalidModel(error) => Self::ListingValidation { - kind: RadrootsSdkListingValidationErrorKind::from(&error), - message: error.to_string(), - }, - error => Self::ListingEdit { - message: error.to_string(), - }, - } - } -} - -#[cfg(feature = "runtime")] -impl From<radroots_trade::operational_listing::RadrootsOperationalListingMutationError> - for RadrootsSdkError -{ - fn from( - error: radroots_trade::operational_listing::RadrootsOperationalListingMutationError, - ) -> Self { - Self::ListingMutation { - message: error.to_string(), - } - } -} - -#[cfg(feature = "runtime")] -impl From<radroots_outbox::RadrootsOutboxError> for RadrootsSdkError { - fn from(error: radroots_outbox::RadrootsOutboxError) -> Self { - match error { - radroots_outbox::RadrootsOutboxError::EmptyDeliveryTargets => { - Self::empty_transport_targets("outbox enqueue") - } - radroots_outbox::RadrootsOutboxError::IdempotencyConflict { - operation_kind, - expected_pubkey, - existing_digest, - new_digest, - .. - } => Self::IdempotencyConflict { - operation_kind, - expected_pubkey_prefix: redacted_prefix(expected_pubkey.as_str()), - existing_digest_prefix: redacted_prefix(existing_digest.as_str()), - new_digest_prefix: redacted_prefix(new_digest.as_str()), - }, - error => Self::Outbox { - message: error.to_string(), - }, - } - } -} - -#[cfg(feature = "runtime")] -impl From<radroots_transport::RadrootsTransportError> for RadrootsSdkError { - fn from(error: radroots_transport::RadrootsTransportError) -> Self { - match error { - radroots_transport::RadrootsTransportError::EmptyTargetSet => { - Self::empty_transport_targets("transport target set") - } - error => Self::Transport { - message: error.to_string(), - }, - } - } -} - -#[cfg(feature = "runtime")] -impl From<radroots_transport_nostr::RadrootsRelayTransportError> for RadrootsSdkError { - fn from(error: radroots_transport_nostr::RadrootsRelayTransportError) -> Self { - match error { - radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlParse { - url, - reason, - } => Self::invalid_relay_url(url, reason), - radroots_transport_nostr::RadrootsRelayTransportError::WsRequiresLocalhostPolicy { - url, - } => Self::invalid_relay_url(url, "ws relay URL requires localhost policy"), - radroots_transport_nostr::RadrootsRelayTransportError::UnsupportedRelayScheme { - url, - scheme, - } => Self::invalid_relay_url(url, format!("unsupported scheme `{scheme}`")), - radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlUserinfo { url } => { - Self::invalid_relay_url(url, "relay URL must not include userinfo") - } - radroots_transport_nostr::RadrootsRelayTransportError::EmptyRelayHost { url } => { - Self::invalid_relay_url(url, "relay URL must include a host") - } - radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlQueryOrFragment { - url, - } => Self::invalid_relay_url(url, "relay URL must not include query or fragment"), - radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlForbiddenDestination { - url, - reason, - } => Self::invalid_relay_url(url, reason), - radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlResolvedForbiddenDestination { - url, - address, - reason, - } => Self::invalid_relay_url( - url, - format!("relay URL resolved to forbidden address `{address}`: {reason}"), - ), - radroots_transport_nostr::RadrootsRelayTransportError::EmptyTargetSet => { - Self::empty_transport_targets("nostr relay publish") - } - #[cfg(feature = "runtime")] - radroots_transport_nostr::RadrootsRelayTransportError::Outbox(error) => error.into(), - error => Self::Transport { - message: error.to_string(), - }, - } - } -} - -#[cfg(feature = "runtime")] -fn redacted_prefix(value: &str) -> String { - value.chars().take(12).collect() -} - -#[cfg(feature = "runtime")] -fn redacted_relay_url(value: String) -> String { - let redacted = redact_query_or_fragment(value.as_str()); - let Some((scheme, rest)) = redacted.split_once("://") else { - return redacted; - }; - let authority = rest.split('/').next().unwrap_or(rest); - let Some((_, after_userinfo)) = authority.rsplit_once('@') else { - return redacted; - }; - let path = rest.strip_prefix(authority).unwrap_or_default(); - format!("{scheme}://<redacted>@{after_userinfo}{path}") -} - -#[cfg(feature = "runtime")] -fn redact_query_or_fragment(value: &str) -> String { - let Some((index, marker)) = value.char_indices().find_map(|(index, character)| { - matches!(character, '?' | '#').then_some((index, character)) - }) else { - return value.to_owned(); - }; - format!("{}{}<redacted>", &value[..index], marker) -} - -#[cfg(test)] -#[path = "../tests/unit/error_tests.rs"] -mod tests; +//! SDK-owned error boundary. diff --git a/crates/sdk/src/farm.rs b/crates/sdk/src/farm.rs @@ -1,9 +1 @@ -pub use radroots_event::farm::*; - -pub use radroots_event_codec::encode::EventEncodeError; - -use radroots_event::wire::Nip01EventWireParts; - -pub fn build_draft(farm: &Farm) -> Result<Nip01EventWireParts, EventEncodeError> { - radroots_event_codec::encode::farm::to_wire_parts(farm) -} +//! Farm product operations. diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -1,201 +1,18 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![forbid(unsafe_code)] +//! Host-neutral asynchronous client engine for Radroots. +//! +//! This checkpoint establishes the final package and module ownership +//! boundary. Client behavior is introduced through the ordered SDK refactor. -#[cfg(not(feature = "std"))] -extern crate alloc; +#![forbid(unsafe_code)] -#[cfg(feature = "runtime")] -mod actor_json; -#[cfg(any( - feature = "radrootsd-execution", - feature = "transport-nostr-client", - feature = "signer-adapters" -))] -pub mod adapters; -#[cfg(feature = "runtime")] -mod error; -#[cfg(feature = "runtime")] -mod farm; -#[cfg(feature = "runtime")] -mod farms_runtime; -#[cfg(all(test, feature = "runtime", feature = "signer-adapters"))] -#[path = "../tests/support/fixture_signer.rs"] -pub(crate) mod fixture_signer; -#[cfg(feature = "runtime")] -mod geonames; -#[cfg(feature = "runtime")] -mod idempotency; -#[cfg(feature = "identity-models")] -pub mod identity; -#[cfg(feature = "knowledge")] -pub mod knowledge; -#[cfg(feature = "runtime")] -mod listings_runtime; -#[cfg(feature = "runtime")] -mod privacy; -#[cfg(feature = "runtime")] -mod private_store; -#[cfg(feature = "runtime")] -mod product_clients; -#[cfg(feature = "runtime")] -mod runtime; -#[cfg(all(test, feature = "runtime"))] -#[path = "../tests/support/serializer_failure.rs"] -pub(crate) mod serializer_failure; -#[cfg(all(feature = "runtime", feature = "signer-adapters"))] -mod signer_provider; -#[cfg(feature = "runtime")] -mod studio_store; -#[cfg(feature = "runtime")] -mod sync_runtime; -#[cfg(feature = "runtime")] -mod trade_runtime; -#[cfg(feature = "runtime")] +pub mod capability; +pub mod client; +pub mod diagnostics; +pub mod error; +pub mod farm; +pub mod listing; +pub mod signing; +pub mod storage; +pub mod sync; +pub mod trade; pub mod transport; -#[cfg(feature = "runtime")] -mod workflow_runtime; - -#[cfg(feature = "runtime")] -pub use crate::error::{ - RADROOTS_SDK_ERROR_CATALOG, RadrootsSdkError, RadrootsSdkErrorCatalogEntry, - RadrootsSdkErrorClass, RadrootsSdkGeoNamesErrorKind, RadrootsSdkListingValidationErrorKind, - RadrootsSdkRecoveryAction, RadrootsSdkTradeErrorKind, radroots_sdk_error_catalog, -}; -#[cfg(feature = "runtime")] -pub use crate::farms_runtime::{ - FARM_PUBLISH_OPERATION_KIND, FarmEnqueuePublishRequest, FarmEnqueueReceipt, - FarmPreparePublishRequest, FarmPrivateLocationClearReceipt, FarmPrivateLocationClearRequest, - FarmPrivateLocationInput, FarmPrivateLocationLookupCandidate, FarmPrivateLocationLookupReceipt, - FarmPrivateLocationReceipt, FarmPrivateLocationSetRequest, FarmPrivateLocationSetResult, - FarmPrivateLocationUpsertRequest, FarmPublishPlan, SdkExactLocation, SdkPublicLocality, -}; -#[cfg(feature = "runtime")] -pub use crate::geonames::{ - GEONAMES_1_0_ASSET, GEONAMES_ASSET_BYTE_SIZE, GEONAMES_ASSET_FILE_NAME, GEONAMES_ASSET_HOST, - GEONAMES_ASSET_SHA256, GEONAMES_ASSET_URL, GEONAMES_ASSET_VERSION, GeoNamesAssetFetcher, - GeoNamesAssetSpec, GeoNamesAssetState, GeoNamesAssetStatus, GeoNamesBlockingHttpFetcher, - Geocoder, GeocoderCountryListResult, GeocoderError, GeocoderLocalityCandidate, - GeocoderLocalityInput, GeocoderLocalityLookup, GeocoderLocalityQuery, GeocoderPoint, - GeocoderReverseOptions, GeocoderReverseResult, GeocoderStructuredLocalityQuery, - RadrootsGeoNamesConfig, -}; -#[cfg(feature = "runtime")] -pub use crate::idempotency::{ - SDK_IDEMPOTENCY_KEY_MAX_LEN, SdkIdempotencyKey, SdkTradeIdempotencyRecord, -}; -#[cfg(feature = "knowledge")] -pub use crate::knowledge::{ - AddressableRef, DraftError, EventDraft, EventEnvelope, EventRef, KIND_FILE_METADATA, - KIND_KNOWLEDGE_CLAIM, KIND_KNOWLEDGE_FIELD_REPORT, KIND_KNOWLEDGE_RELATION, - KIND_KNOWLEDGE_REVIEW, KIND_KNOWLEDGE_SOURCE, KIND_WIKI_ARTICLE, KIND_WIKI_MERGE_REQUEST, - KIND_WIKI_REDIRECT, KNOWLEDGE_CLAIM_CONTRACT_ID, KNOWLEDGE_FIELD_REPORT_CONTRACT_ID, - KNOWLEDGE_RELATION_CONTRACT_ID, KNOWLEDGE_REVIEW_CONTRACT_ID, KNOWLEDGE_SOURCE_CONTRACT_ID, - KnowledgeChangeProposal, KnowledgeCitationSpan, KnowledgeClaim, KnowledgeCodec, - KnowledgeDraftBuilder, KnowledgeEventBuilder, KnowledgeFieldContext, KnowledgeFieldReport, - KnowledgeLocation, KnowledgeLocationPrecision, KnowledgeNodeRef, KnowledgeObservation, - KnowledgeObservationValue, KnowledgeRelation, KnowledgeReview, KnowledgeReviewScope, - KnowledgeReviewScore, KnowledgeReviewTarget, KnowledgeSource, Nip01EventWireParts, - RADROOTS_CONTRIBUTION_ATTESTATION_SCHEMA, RADROOTS_EVIDENCE_BOUNTY_SCHEMA, - RADROOTS_KNOWLEDGE_CHANGE_PROPOSAL_SCHEMA, RADROOTS_KNOWLEDGE_CLAIM_SCHEMA, - RADROOTS_KNOWLEDGE_CONTRACT_MANIFEST_SCHEMA_VERSION, RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA, - RADROOTS_KNOWLEDGE_RELATION_SCHEMA, RADROOTS_KNOWLEDGE_REVIEW_SCHEMA, - RADROOTS_KNOWLEDGE_SCHEMA_VERSION, RADROOTS_KNOWLEDGE_SOURCE_SCHEMA, - RADROOTS_WIKI_D_TAG_MAX_LEN, RadrootsContractValidatedEvent, RadrootsDecodeError, - RadrootsDecodedEvent, RadrootsEncodeError, RadrootsIdVerifiedEvent, - RadrootsKnowledgeBuilderError, RadrootsKnowledgeClaimBuilder, - RadrootsKnowledgeContractManifest, RadrootsKnowledgeContractManifestEntry, - RadrootsKnowledgeFieldReportBuilder, RadrootsKnowledgeManifestCodecSupport, - RadrootsKnowledgeManifestDiscriminator, RadrootsKnowledgeManifestTagContract, - RadrootsKnowledgeRelationBuilder, RadrootsKnowledgeReviewBuilder, - RadrootsKnowledgeSourceBuilder, RadrootsNip01VerificationError, RadrootsSdkKnowledgeError, - RadrootsSignatureVerifiedEvent, RadrootsWikiArticleBuilder, RadrootsWikiMergeRequestBuilder, - RadrootsWikiRedirectBuilder, RightsAssertion, WIKI_ARTICLE_CONTRACT_ID, - WIKI_MERGE_REQUEST_CONTRACT_ID, WIKI_REDIRECT_CONTRACT_ID, WikiArticle, WikiArticleVersionRef, - WikiDTagError, WikiMergeRequest, WikiRedirect, build_knowledge_claim_event, - build_knowledge_field_report_event, build_knowledge_relation_event, - build_knowledge_review_event, build_knowledge_source_event, build_wiki_article_event, - build_wiki_merge_request_event, build_wiki_redirect_event, contract_manifest, - contract_manifest_json, contract_manifest_sha256, normalize_wiki_d_tag, - prepare_knowledge_claim_draft, prepare_knowledge_field_report_draft, - prepare_knowledge_relation_draft, prepare_knowledge_review_draft, - prepare_knowledge_source_draft, prepare_wiki_article_draft, prepare_wiki_merge_request_draft, - prepare_wiki_redirect_draft, validate_wiki_d_tag, verify_and_decode_radroots_event, -}; -#[cfg(feature = "runtime")] -pub use crate::listings_runtime::{ - LISTING_PUBLISH_OPERATION_KIND, ListingEnqueuePublishRequest, ListingEnqueueReceipt, - ListingPreparePublishRequest, ListingPublishPlan, SdkMutationState, -}; -#[cfg(feature = "runtime")] -pub use crate::privacy::{ - PrivacyPreflightConfirmation, PrivacyPreflightReceipt, PrivacyPreflightStatus, - ProductSensitivityField, -}; -#[cfg(feature = "runtime")] -pub use crate::product_clients::{ - FarmsClient, GeoNamesClient, ListingsClient, MarketClient, SyncClient, TradesClient, -}; -#[cfg(feature = "runtime")] -pub use crate::runtime::{ - BackupReceipt, BackupRequest, IntegrityReceipt, IntegrityRequest, RadrootsClient, - RadrootsClientBuilder, RadrootsSdkClock, RadrootsSdkStorageConfig, RadrootsSdkStoragePaths, - RadrootsSdkTimestamp, RestoreArchive, RestoreReceipt, RestoreRequest, SdkBackupKeyReference, - SdkBackupManifest, SdkBackupManifestKind, SdkBackupMemberHash, SdkBackupMemberHashes, - SdkBackupRecoveryManifest, SdkBackupState, SdkBackupVerification, SdkEventStoreStorageStatus, - SdkOutboxStorageStatus, SdkPrivateStoreStorageStatus, SdkRestoreFinalization, SdkRestoreState, - SdkSqliteStoreStatus, SdkSqliteWalCheckpointReceipt, SdkSqliteWalStatus, SdkStorageKind, - SdkStudioStoreStorageStatus, StorageCheckpointReceipt, StorageCheckpointRequest, - StorageStatusReceipt, StorageStatusRequest, -}; -#[cfg(all(feature = "runtime", feature = "signer-adapters"))] -#[doc(hidden)] -pub use crate::signer_provider::{ - RADROOTS_SDK_MYC_NIP46_DEFAULT_REQUEST_TIMEOUT_MS, - RADROOTS_SDK_MYC_NIP46_PRODUCT_SIGN_EVENT_KINDS, RadrootsSdkLocalKeySigner, - RadrootsSdkLocalSignerCapability, RadrootsSdkMycNip46RequestPolicy, RadrootsSdkMycNip46Signer, - RadrootsSdkNip46ClientKey, RadrootsSdkNip46Transport, RadrootsSdkNip46TransportFuture, - RadrootsSdkSignReceipt, RadrootsSdkSignRequest, RadrootsSdkSignerCapability, - RadrootsSdkSignerMode, RadrootsSdkSignerProgress, RadrootsSdkSignerProgressSink, - RadrootsSdkSignerProvider, RadrootsSdkSignerState, RadrootsSdkSignerStatus, - radroots_sdk_myc_nip46_product_permission_strings, radroots_sdk_myc_nip46_product_permissions, -}; -#[cfg(feature = "runtime")] -pub use crate::sync_runtime::{ - PUSH_OUTBOX_DEFAULT_CLAIM_TTL_MS, PUSH_OUTBOX_DEFAULT_LIMIT, - PUSH_OUTBOX_DEFAULT_NEXT_ATTEMPT_DELAY_MS, PUSH_OUTBOX_MAX_LIMIT, PushOutboxEventReceipt, - PushOutboxEventState, PushOutboxReceipt, PushOutboxRequest, PushOutboxTargetOutcomeKind, - PushOutboxTargetReceipt, PushOutboxTransportOutcomeKind, ReticulumTryNowRequest, - SYNC_PROJECTION_REFRESH_DEFAULT_LIMIT, SYNC_PROJECTION_REFRESH_MAX_LIMIT, SdkRelayAuthPolicy, - SyncEventStoreStatus, SyncOutboxStatus, SyncProjectionRefreshReceipt, - SyncProjectionRefreshRequest, SyncStatusReceipt, SyncStatusRequest, SyncStatusSource, - SyncTransportOperationCapabilitiesSummary, SyncTransportProfileSummary, - SyncTransportStatusSummary, SyncTransportTargetSummary, -}; -#[cfg(feature = "runtime")] -pub use crate::trade_runtime::{ - CancelTradeRequest, DecideCandidateRequest, EvidenceRefreshReceipt, EvidenceView, - GetTradeRequest, InspectEvidenceRequest, ListTradesRequest, Page, ProposeRevisionRequest, - RefreshTradeEvidenceRequest, ResumeOperationRequest, SubmitProposalRequest, - TRADE_CANCEL_OPERATION_KIND, TRADE_DECIDE_CANDIDATE_OPERATION_KIND, - TRADE_PROPOSE_REVISION_OPERATION_KIND, TRADE_QUERY_DEFAULT_LIMIT, TRADE_QUERY_MAX_LIMIT, - TRADE_RESUME_OPERATION_KIND, TRADE_RUNTIME_CAPABILITY_API_VERSION, - TRADE_RUNTIME_PRIVATE_STORAGE_PROFILE_ID, TRADE_RUNTIME_PROTOCOL_PROFILE_ID, - TRADE_RUNTIME_STORAGE_PROFILE_ID, TRADE_RUNTIME_WIRE_PROFILE_ID, - TRADE_SUBMIT_PROPOSAL_OPERATION_KIND, TradeCommandLifecycleState, TradeCommandReceipt, - TradeCommandService, TradeCoreMvpCapabilityReport, TradeListAnyOf, TradeListFilter, - TradeListSort, TradeOptionalIntegrationCapabilityReport, TradePrivateArtifactDeleteReceipt, - TradePrivateArtifactDeleteRequest, TradePrivateArtifactKind, TradePrivateArtifactOpenReceipt, - TradePrivateArtifactOpenRequest, TradePrivateArtifactSealReceipt, - TradePrivateArtifactSealRequest, TradePrivateTermsAvailabilityView, - TradeProtocolCapabilityReport, TradeQueryService, TradeRuntimeCapabilityReport, - TradeStatusView, TradeStorageCapabilityReport, TradeSummaryView, -}; -#[cfg(feature = "runtime")] -pub use crate::transport::{ - MeshScopeId, MultiTargetProfile, NostrProfile, NostrRelayUrlPolicy, PublishMode, - RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryTargetStatus, - RadrootsTransportOutcome, RadrootsTransportSatisfactionClass, RadrootsTransportTargetReceipt, - RadrootsdExecutionAuth, RadrootsdExecutionProfile, ReticulumAgentEndpoint, ReticulumBehavior, - ReticulumProfile, SDK_TRANSPORT_TARGET_MAX_COUNT, SatisfactionPolicy, TargetPolicy, TargetSet, - TransportId, TransportProfile, TransportReceipt, -}; diff --git a/crates/sdk/src/listing.rs b/crates/sdk/src/listing.rs @@ -0,0 +1 @@ +//! Listing product operations. diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs @@ -0,0 +1 @@ +//! Signing capability composition. diff --git a/crates/sdk/src/storage.rs b/crates/sdk/src/storage.rs @@ -0,0 +1 @@ +//! Storage capability composition. diff --git a/crates/sdk/src/sync.rs b/crates/sdk/src/sync.rs @@ -0,0 +1 @@ +//! Synchronization capability composition. diff --git a/crates/sdk/src/trade.rs b/crates/sdk/src/trade.rs @@ -0,0 +1 @@ +//! Trade commands and queries. diff --git a/crates/sdk/src/transport.rs b/crates/sdk/src/transport.rs @@ -1,773 +1 @@ -use crate::RadrootsSdkError; -use radroots_transport::target::{TargetFingerprint, TargetScope, TargetSet as TransportTargetSet}; -use radroots_transport::{ - RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity, - RadrootsTransportImplementationState, RadrootsTransportSatisfactionPolicy, - RadrootsTransportStatus, Target, -}; -use radroots_transport_nostr::{RadrootsRelayUrlPolicy, RelayUrl}; -use radroots_transport_reticulum::{ - RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID, - RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, -}; -use serde::ser::{SerializeStruct, Serializer}; -use std::collections::BTreeSet; - -pub use radroots_transport::{ - RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryTargetStatus, - RadrootsTransportOutcome, RadrootsTransportSatisfactionClass, RadrootsTransportTargetReceipt, - TransportId, -}; - -pub const SDK_TRANSPORT_TARGET_MAX_COUNT: usize = 20; -pub const RETICULUM_AGENT_ENDPOINT_PREFIX: &str = "reticulum-agent:"; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PublishMode { - DryRun, - EnqueueOnly, - EnqueueAndPublish, -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum SatisfactionPolicy { - NoWait, - AnyAccepted, - AllAccepted, - QuorumAccepted { - threshold: u16, - }, - AnyDelivered, - AllDelivered, - QuorumDelivered { - threshold: u16, - }, - RequiredAcceptedTargets { - target_fingerprints: Vec<TargetFingerprint>, - }, - RequiredDeliveredTargets { - target_fingerprints: Vec<TargetFingerprint>, - }, -} - -impl SatisfactionPolicy { - pub fn quorum_accepted(threshold: u16) -> Result<Self, RadrootsSdkError> { - validate_satisfaction_threshold(threshold)?; - Ok(Self::QuorumAccepted { threshold }) - } - - pub fn quorum_delivered(threshold: u16) -> Result<Self, RadrootsSdkError> { - validate_satisfaction_threshold(threshold)?; - Ok(Self::QuorumDelivered { threshold }) - } - - pub fn required_accepted_targets<I, S>(targets: I) -> Result<Self, RadrootsSdkError> - where - I: IntoIterator<Item = S>, - S: AsRef<str>, - { - Ok(Self::RequiredAcceptedTargets { - target_fingerprints: required_target_fingerprints(targets)?, - }) - } - - pub fn required_delivered_targets<I, S>(targets: I) -> Result<Self, RadrootsSdkError> - where - I: IntoIterator<Item = S>, - S: AsRef<str>, - { - Ok(Self::RequiredDeliveredTargets { - target_fingerprints: required_target_fingerprints(targets)?, - }) - } - - pub fn is_no_wait(&self) -> bool { - matches!(self, Self::NoWait) - } - - pub(crate) fn transport_satisfaction_policy( - &self, - ) -> Result<RadrootsTransportSatisfactionPolicy, RadrootsSdkError> { - Ok(match self { - Self::NoWait => RadrootsTransportSatisfactionPolicy::no_wait(), - Self::AnyAccepted => RadrootsTransportSatisfactionPolicy::any_accepted(), - Self::AllAccepted => RadrootsTransportSatisfactionPolicy::all_accepted(), - Self::QuorumAccepted { threshold } => { - RadrootsTransportSatisfactionPolicy::quorum_accepted(*threshold) - } - Self::AnyDelivered => RadrootsTransportSatisfactionPolicy::any_delivered(), - Self::AllDelivered => RadrootsTransportSatisfactionPolicy::all_delivered(), - Self::QuorumDelivered { threshold } => { - RadrootsTransportSatisfactionPolicy::quorum_delivered(*threshold) - } - Self::RequiredAcceptedTargets { - target_fingerprints, - } => RadrootsTransportSatisfactionPolicy::required_targets( - RadrootsTransportSatisfactionClass::Accepted, - target_fingerprints.clone(), - )?, - Self::RequiredDeliveredTargets { - target_fingerprints, - } => RadrootsTransportSatisfactionPolicy::required_targets( - RadrootsTransportSatisfactionClass::Delivered, - target_fingerprints.clone(), - )?, - }) - } -} - -fn validate_satisfaction_threshold(threshold: u16) -> Result<(), RadrootsSdkError> { - if threshold == 0 { - return Err(RadrootsSdkError::InvalidRequest { - message: "satisfaction policy threshold must require at least one target".to_owned(), - }); - } - Ok(()) -} - -fn required_target_fingerprints<I, S>( - targets: I, -) -> Result<Vec<TargetFingerprint>, RadrootsSdkError> -where - I: IntoIterator<Item = S>, - S: AsRef<str>, -{ - let fingerprints = targets - .into_iter() - .map(|target| TargetFingerprint::parse(target.as_ref())) - .collect::<Result<Vec<_>, _>>()?; - RadrootsTransportSatisfactionPolicy::required_targets( - RadrootsTransportSatisfactionClass::Accepted, - fingerprints.clone(), - )?; - Ok(fingerprints) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum NostrRelayUrlPolicy { - Public, - Localhost, -} - -impl NostrRelayUrlPolicy { - pub(crate) fn nostr_transport_policy(self) -> RadrootsRelayUrlPolicy { - match self { - Self::Public => RadrootsRelayUrlPolicy::Public, - Self::Localhost => RadrootsRelayUrlPolicy::Localhost, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum TargetPolicy { - Explicit(TargetSet), - DefaultProfile, - LocalOnly, - MeshScope(MeshScopeId), -} - -impl TargetPolicy { - pub fn explicit(targets: TargetSet) -> Self { - Self::Explicit(targets) - } - - pub fn try_nostr_relays<I, S>( - relays: I, - url_policy: NostrRelayUrlPolicy, - ) -> Result<Self, RadrootsSdkError> - where - I: IntoIterator<Item = S>, - S: AsRef<str>, - { - Ok(Self::Explicit(TargetSet::nostr_relays(relays, url_policy)?)) - } - - pub fn default_profile() -> Self { - Self::DefaultProfile - } - - pub fn local_only() -> Self { - Self::LocalOnly - } - - pub fn mesh_scope(scope: MeshScopeId) -> Self { - Self::MeshScope(scope) - } -} - -impl serde::Serialize for TargetPolicy { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - match self { - Self::Explicit(targets) => { - let mut state = serializer.serialize_struct("TargetPolicy", 3)?; - state.serialize_field("kind", "explicit")?; - state.serialize_field("targets", targets.targets())?; - state.serialize_field("canonical_targets", targets.canonical_targets())?; - state.end() - } - Self::DefaultProfile => { - let mut state = serializer.serialize_struct("TargetPolicy", 1)?; - state.serialize_field("kind", "default_profile")?; - state.end() - } - Self::LocalOnly => { - let mut state = serializer.serialize_struct("TargetPolicy", 1)?; - state.serialize_field("kind", "local_only")?; - state.end() - } - Self::MeshScope(scope) => { - let mut state = serializer.serialize_struct("TargetPolicy", 2)?; - state.serialize_field("kind", "mesh_scope")?; - state.serialize_field("scope", scope)?; - state.end() - } - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(transparent)] -pub struct MeshScopeId(TargetScope); - -impl MeshScopeId { - pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsSdkError> { - Ok(Self(TargetScope::parse(raw)?)) - } - - pub fn local_reticulum() -> Self { - Self(TargetScope::parse(RADROOTS_RETICULUM_SCOPE_ID).expect("Reticulum scope")) - } - - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - pub(crate) fn transport_scope(&self) -> TargetScope { - self.0.clone() - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TargetSet { - targets: Vec<Target>, - canonical_targets: Vec<String>, -} - -impl TargetSet { - pub fn nostr_relays<I, S>( - relays: I, - policy: NostrRelayUrlPolicy, - ) -> Result<Self, RadrootsSdkError> - where - I: IntoIterator<Item = S>, - S: AsRef<str>, - { - let mut targets = Vec::new(); - for relay in relays { - let normalized = normalized_nostr_relay_url(relay.as_ref(), policy)?; - targets.push(Target::new(TransportId::NOSTR, normalized)?); - } - Self::from_transport_targets(targets) - } - - pub fn transport_targets(targets: Vec<Target>) -> Result<Self, RadrootsSdkError> { - Self::from_transport_targets(targets) - } - - pub fn targets(&self) -> &[Target] { - self.targets.as_slice() - } - - pub fn canonical_targets(&self) -> &[String] { - self.canonical_targets.as_slice() - } - - pub fn transport_target_set(&self) -> Result<TransportTargetSet, RadrootsSdkError> { - Ok(TransportTargetSet::new(self.targets.clone())?) - } - - pub fn nostr_relay_urls(&self) -> Vec<String> { - self.targets - .iter() - .filter(|target| target.kind() == &TransportId::NOSTR) - .map(|target| target.uri().as_str().to_owned()) - .collect() - } - - pub fn into_targets(self) -> Vec<Target> { - self.targets - } - - pub fn len(&self) -> usize { - self.targets.len() - } - - pub fn is_empty(&self) -> bool { - self.targets.is_empty() - } - - fn from_transport_targets(targets: Vec<Target>) -> Result<Self, RadrootsSdkError> { - if targets.is_empty() { - return Err(RadrootsSdkError::empty_transport_targets( - "sdk transport target set", - )); - } - if targets.len() > SDK_TRANSPORT_TARGET_MAX_COUNT { - return Err(RadrootsSdkError::transport_target_limit_exceeded( - SDK_TRANSPORT_TARGET_MAX_COUNT, - targets.len(), - )); - } - for target in &targets { - if target.kind() == &TransportId::RETICULUM - && target.uri().as_str() != RADROOTS_RETICULUM_ENDPOINT_URI - { - return Err(RadrootsSdkError::InvalidRequest { - message: format!( - "Reticulum endpoint must be {RADROOTS_RETICULUM_ENDPOINT_URI}" - ), - }); - } - } - TransportTargetSet::new(targets.clone())?; - let canonical_targets = targets - .iter() - .map(|target| target.fingerprint().to_string()) - .collect::<BTreeSet<_>>() - .into_iter() - .collect(); - Ok(Self { - targets, - canonical_targets, - }) - } -} - -impl serde::Serialize for TargetSet { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("TargetSet", 2)?; - state.serialize_field("targets", self.targets())?; - state.serialize_field("canonical_targets", self.canonical_targets())?; - state.end() - } -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct NostrProfile { - target_set: TargetSet, -} - -impl NostrProfile { - pub fn new<I, S>(relays: I, policy: NostrRelayUrlPolicy) -> Result<Self, RadrootsSdkError> - where - I: IntoIterator<Item = S>, - S: AsRef<str>, - { - Ok(Self { - target_set: TargetSet::nostr_relays(relays, policy)?, - }) - } - - pub fn target_set(&self) -> &TargetSet { - &self.target_set - } - - pub fn relay_urls(&self) -> Vec<String> { - self.target_set.nostr_relay_urls() - } -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct ReticulumProfile { - endpoint_uri: String, - scope: MeshScopeId, - agent_endpoint: Option<ReticulumAgentEndpoint>, - behavior: ReticulumBehavior, -} - -impl ReticulumProfile { - pub fn deferred_until_implemented() -> Self { - Self { - endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(), - scope: MeshScopeId::local_reticulum(), - agent_endpoint: None, - behavior: ReticulumBehavior::RejectDeliveryAttempts, - } - } - - pub fn with_behavior(mut self, behavior: ReticulumBehavior) -> Self { - self.behavior = behavior; - self - } - - pub fn endpoint_uri(&self) -> &str { - self.endpoint_uri.as_str() - } - - pub fn scope(&self) -> &MeshScopeId { - &self.scope - } - - pub fn agent_endpoint(&self) -> Option<&ReticulumAgentEndpoint> { - self.agent_endpoint.as_ref() - } - - pub fn with_agent_endpoint(mut self, agent_endpoint: ReticulumAgentEndpoint) -> Self { - self.agent_endpoint = Some(agent_endpoint); - self - } - - pub fn with_scope(mut self, scope: MeshScopeId) -> Self { - self.scope = scope; - self - } - - pub fn behavior(&self) -> ReticulumBehavior { - self.behavior - } - - pub fn target_set(&self) -> Result<TargetSet, RadrootsSdkError> { - if self.endpoint_uri.as_str() != RADROOTS_RETICULUM_ENDPOINT_URI { - return Err(radroots_transport::RadrootsTransportError::InvalidTargetUri.into()); - } - TargetSet::transport_targets(vec![Target::new_with_metadata( - TransportId::RETICULUM, - self.endpoint_uri.as_str(), - Some(self.scope.transport_scope()), - None, - )?]) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(transparent)] -pub struct ReticulumAgentEndpoint(String); - -impl ReticulumAgentEndpoint { - pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsSdkError> { - let uri = raw.as_ref(); - let Some(suffix) = uri.strip_prefix(RETICULUM_AGENT_ENDPOINT_PREFIX) else { - return Err(RadrootsSdkError::InvalidRequest { - message: "Reticulum agent endpoint is invalid".to_owned(), - }); - }; - if uri.is_empty() - || uri != uri.trim() - || suffix.is_empty() - || uri - .chars() - .any(|ch| ch.is_ascii_control() || ch.is_ascii_whitespace()) - { - return Err(RadrootsSdkError::InvalidRequest { - message: "Reticulum agent endpoint is invalid".to_owned(), - }); - } - Ok(Self(uri.to_owned())) - } - - pub fn as_str(&self) -> &str { - self.0.as_str() - } -} - -impl Default for ReticulumProfile { - fn default() -> Self { - Self::deferred_until_implemented() - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum ReticulumBehavior { - RejectDeliveryAttempts, - DeferDeliveryPlans, -} - -impl ReticulumBehavior { - pub fn as_str(self) -> &'static str { - match self { - Self::RejectDeliveryAttempts => "reject_delivery_attempts", - Self::DeferDeliveryPlans => "defer_delivery_plans", - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct MultiTargetProfile { - nostr: NostrProfile, - reticulum: ReticulumProfile, -} - -impl MultiTargetProfile { - pub fn new(nostr: NostrProfile, reticulum: ReticulumProfile) -> Self { - Self { nostr, reticulum } - } - - pub fn nostr(&self) -> &NostrProfile { - &self.nostr - } - - pub fn reticulum(&self) -> &ReticulumProfile { - &self.reticulum - } -} - -#[derive(Clone, Default, PartialEq, Eq)] -pub enum RadrootsdExecutionAuth { - #[default] - None, - BearerToken(String), -} - -impl core::fmt::Debug for RadrootsdExecutionAuth { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::None => f.write_str("None"), - Self::BearerToken(_) => f.write_str("BearerToken(<redacted>)"), - } - } -} - -impl serde::Serialize for RadrootsdExecutionAuth { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - let mut state = serializer.serialize_struct("RadrootsdExecutionAuth", 1)?; - match self { - Self::None => state.serialize_field("kind", "none")?, - Self::BearerToken(_) => state.serialize_field("kind", "bearer_token")?, - } - state.end() - } -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct RadrootsdExecutionProfile { - endpoint_url: String, - auth: RadrootsdExecutionAuth, -} - -impl RadrootsdExecutionProfile { - pub fn new(endpoint_url: impl Into<String>) -> Self { - Self { - endpoint_url: endpoint_url.into(), - auth: RadrootsdExecutionAuth::None, - } - } - - pub fn with_bearer_token(mut self, token: impl Into<String>) -> Self { - self.auth = RadrootsdExecutionAuth::BearerToken(token.into()); - self - } - - pub fn endpoint_url(&self) -> &str { - self.endpoint_url.as_str() - } - - pub fn auth(&self) -> &RadrootsdExecutionAuth { - &self.auth - } -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "snake_case", tag = "kind")] -#[non_exhaustive] -pub enum TransportProfile { - #[default] - LocalOnly, - Nostr { - profile: NostrProfile, - }, - Reticulum { - profile: ReticulumProfile, - }, - MultiTarget { - profile: MultiTargetProfile, - }, -} - -impl TransportProfile { - pub fn local_only() -> Self { - Self::LocalOnly - } - - pub fn nostr(profile: NostrProfile) -> Self { - Self::Nostr { profile } - } - - pub fn reticulum(profile: ReticulumProfile) -> Self { - Self::Reticulum { profile } - } - - pub fn multi_target(profile: MultiTargetProfile) -> Self { - Self::MultiTarget { profile } - } - - pub(crate) fn transport_profile_id(&self) -> &'static str { - match self { - Self::LocalOnly => "local_only", - Self::Nostr { .. } => "nostr", - Self::Reticulum { .. } => "reticulum", - Self::MultiTarget { .. } => "multi_target", - } - } - - pub(crate) fn target_set(&self) -> Result<Option<TargetSet>, RadrootsSdkError> { - match self { - Self::LocalOnly => Ok(None), - Self::Nostr { profile } => Ok(Some(profile.target_set().clone())), - Self::Reticulum { profile } => Ok(Some(profile.target_set()?)), - Self::MultiTarget { profile } => { - let mut targets = profile.nostr().target_set().targets().to_vec(); - targets.extend(profile.reticulum().target_set()?.into_targets()); - Ok(Some(TargetSet::transport_targets(targets)?)) - } - } - } - - pub(crate) fn configured_transport_targets(&self) -> Result<Vec<Target>, RadrootsSdkError> { - Ok(self - .target_set()? - .map(TargetSet::into_targets) - .unwrap_or_default()) - } - - pub(crate) fn transport_statuses(&self) -> Vec<RadrootsTransportStatus> { - match self { - Self::LocalOnly => vec![ - RadrootsTransportStatus::new( - TransportId::LOCAL, - true, - RadrootsTransportImplementationState::Real, - false, - "local persistence only", - ) - .with_profile_id(self.transport_profile_id()), - ], - Self::Nostr { .. } => vec![nostr_transport_status(self.transport_profile_id())], - Self::Reticulum { profile } => { - vec![reticulum_transport_status( - self.transport_profile_id(), - profile.endpoint_uri(), - )] - } - Self::MultiTarget { profile } => vec![ - nostr_transport_status(self.transport_profile_id()), - reticulum_transport_status( - self.transport_profile_id(), - profile.reticulum().endpoint_uri(), - ), - ], - } - } - - pub(crate) fn configured_nostr_relay_urls(&self) -> Vec<String> { - match self { - Self::Nostr { profile } => profile.relay_urls(), - Self::MultiTarget { profile } => profile.nostr().relay_urls(), - Self::LocalOnly | Self::Reticulum { .. } => Vec::new(), - } - } -} - -fn nostr_transport_status(profile_id: &str) -> RadrootsTransportStatus { - RadrootsTransportStatus::new( - TransportId::NOSTR, - true, - RadrootsTransportImplementationState::Real, - true, - "ready", - ) - .with_profile_id(profile_id) -} - -fn reticulum_transport_status(profile_id: &str, endpoint_uri: &str) -> RadrootsTransportStatus { - RadrootsTransportStatus::new( - TransportId::RETICULUM, - true, - RadrootsTransportImplementationState::Real, - false, - RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, - ) - .with_profile_id(profile_id) - .with_endpoint_uri(endpoint_uri) - .with_maturity(RadrootsTransportCapabilityMaturity::Preview) - .with_availability(RadrootsTransportCapabilityAvailability::Unavailable) -} - -impl From<RadrootsTransportDeliveryReceipt> for TransportReceipt { - fn from(value: RadrootsTransportDeliveryReceipt) -> Self { - Self { - request_id: value.request_id().to_owned(), - target_set: value.target_set().clone(), - target_receipts: value.target_receipts().to_vec(), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct TransportReceipt { - request_id: String, - target_set: TransportTargetSet, - target_receipts: Vec<RadrootsTransportTargetReceipt>, -} - -impl TransportReceipt { - pub fn request_id(&self) -> &str { - self.request_id.as_str() - } - - pub fn target_set(&self) -> &TransportTargetSet { - &self.target_set - } - - pub fn target_receipts(&self) -> &[RadrootsTransportTargetReceipt] { - self.target_receipts.as_slice() - } - - pub fn satisfied_target_count( - &self, - satisfaction_class: RadrootsTransportSatisfactionClass, - ) -> usize { - self.target_receipts - .iter() - .filter(|receipt| receipt.status.counts_as_satisfied(satisfaction_class)) - .count() - } - - pub fn is_satisfied_by(&self, policy: &SatisfactionPolicy) -> Result<bool, RadrootsSdkError> { - let receipt = RadrootsTransportDeliveryReceipt::new( - self.request_id.clone(), - self.target_set.clone(), - self.target_receipts.clone(), - )?; - Ok(receipt.is_satisfied_by(&policy.transport_satisfaction_policy()?)?) - } -} - -fn normalized_nostr_relay_url( - value: &str, - policy: NostrRelayUrlPolicy, -) -> Result<String, RadrootsSdkError> { - let relay = RelayUrl::parse(value, policy.nostr_transport_policy())?; - Ok(relay.into_string()) -} - -#[cfg(test)] -#[path = "../tests/unit/transport_tests.rs"] -mod tests; +//! Transport capability composition. diff --git a/crates/sdk/tests/package_boundary.rs b/crates/sdk/tests/package_boundary.rs @@ -0,0 +1,72 @@ +use std::collections::BTreeSet; + +const MANIFEST: &str = include_str!("../Cargo.toml"); +const ROOT: &str = include_str!("../src/lib.rs"); + +#[test] +fn manifest_has_final_identity_and_dependency_boundary() { + assert!(MANIFEST.contains("name = \"radroots_sdk\"")); + assert!(MANIFEST.contains("publish = false")); + assert!(MANIFEST.contains("version.workspace = true")); + assert!(MANIFEST.contains("name = \"package_boundary\"")); + + let dependencies = dependency_names(MANIFEST); + let expected = BTreeSet::from([ + "radroots_core", + "radroots_event", + "radroots_event_codec", + "radroots_geonames", + "radroots_identity", + "radroots_nostr", + "radroots_nostr_connect", + "radroots_protocol", + "radroots_secrets", + "radroots_signing", + "radroots_storage", + "radroots_storage_sqlite", + "radroots_sync", + "radroots_trade", + "radroots_transport", + "radroots_transport_nostr", + ]); + assert_eq!(dependencies, expected); +} + +#[test] +fn root_declares_exact_final_module_skeleton() { + let actual = ROOT + .lines() + .filter_map(|line| line.trim().strip_prefix("pub mod ")) + .map(|module| module.trim_end_matches(';')) + .collect::<BTreeSet<_>>(); + let expected = BTreeSet::from([ + "capability", + "client", + "diagnostics", + "error", + "farm", + "listing", + "signing", + "storage", + "sync", + "trade", + "transport", + ]); + assert_eq!(actual, expected); + assert!(!ROOT.contains("no_std")); +} + +fn dependency_names(manifest: &str) -> BTreeSet<&str> { + let dependencies = manifest + .split_once("[dependencies]") + .expect("dependency section") + .1 + .split_once("[[test]]") + .expect("test section") + .0; + dependencies + .lines() + .filter_map(|line| line.split_once(" = ").map(|(name, _)| name.trim())) + .filter(|name| name.starts_with("radroots_")) + .collect() +}