From a9300c1b262c806bbc2fa2fb99d5245721ae5a35 Mon Sep 17 00:00:00 2001 From: Thomas Karpiniec Date: Sat, 24 Jan 2026 17:03:56 +1100 Subject: [PATCH] v0.2.0 - make things work well with jacquard schemas and validation --- Cargo.lock | 1788 ++++++++++++++++- Cargo.toml | 36 +- README.md | 78 +- examples/standard_site_sync.rs | 295 --- lexicons-example/Cargo.toml | 29 + .../lexicons/com.atproto.repo.strongRef.json | 15 + .../lexicons/site.standard.document.json | 74 + .../site.standard.graph.subscription.json | 24 + .../lexicons/site.standard.publication.json | 57 + .../lexicons/site.standard.theme.basic.json | 46 + .../lexicons/site.standard.theme.color.json | 62 + lexicons-example/src/builder_types.rs | 43 + lexicons-example/src/com_atproto.rs | 6 + lexicons-example/src/com_atproto/repo.rs | 6 + .../src/com_atproto/repo/strong_ref.rs | 244 +++ lexicons-example/src/lib.rs | 12 + lexicons-example/src/site_standard.rs | 9 + .../src/site_standard/document.rs | 714 +++++++ lexicons-example/src/site_standard/graph.rs | 6 + .../src/site_standard/graph/subscription.rs | 255 +++ .../src/site_standard/publication.rs | 550 +++++ lexicons-example/src/site_standard/theme.rs | 7 + .../src/site_standard/theme/basic.rs | 359 ++++ .../src/site_standard/theme/color.rs | 718 +++++++ standard-site-sync/Cargo.toml | 24 + standard-site-sync/src/main.rs | 275 +++ tapped/Cargo.toml | 28 + {src => tapped/src}/channel.rs | 101 +- {src => tapped/src}/client.rs | 1 - {src => tapped/src}/config.rs | 2 +- {src => tapped/src}/error.rs | 4 + {src => tapped/src}/handle.rs | 7 +- {src => tapped/src}/lib.rs | 5 +- {src => tapped/src}/process.rs | 3 +- {src => tapped/src}/types.rs | 180 +- {tests => tapped/tests}/integration.rs | 32 - 36 files changed, 5459 insertions(+), 636 deletions(-) delete mode 100644 examples/standard_site_sync.rs create mode 100644 lexicons-example/Cargo.toml create mode 100644 lexicons-example/lexicons/com.atproto.repo.strongRef.json create mode 100644 lexicons-example/lexicons/site.standard.document.json create mode 100644 lexicons-example/lexicons/site.standard.graph.subscription.json create mode 100644 lexicons-example/lexicons/site.standard.publication.json create mode 100644 lexicons-example/lexicons/site.standard.theme.basic.json create mode 100644 lexicons-example/lexicons/site.standard.theme.color.json create mode 100644 lexicons-example/src/builder_types.rs create mode 100644 lexicons-example/src/com_atproto.rs create mode 100644 lexicons-example/src/com_atproto/repo.rs create mode 100644 lexicons-example/src/com_atproto/repo/strong_ref.rs create mode 100644 lexicons-example/src/lib.rs create mode 100644 lexicons-example/src/site_standard.rs create mode 100644 lexicons-example/src/site_standard/document.rs create mode 100644 lexicons-example/src/site_standard/graph.rs create mode 100644 lexicons-example/src/site_standard/graph/subscription.rs create mode 100644 lexicons-example/src/site_standard/publication.rs create mode 100644 lexicons-example/src/site_standard/theme.rs create mode 100644 lexicons-example/src/site_standard/theme/basic.rs create mode 100644 lexicons-example/src/site_standard/theme/color.rs create mode 100644 standard-site-sync/Cargo.toml create mode 100644 standard-site-sync/src/main.rs create mode 100644 tapped/Cargo.toml rename {src => tapped/src}/channel.rs (64%) rename {src => tapped/src}/client.rs (99%) rename {src => tapped/src}/config.rs (99%) rename {src => tapped/src}/error.rs (94%) rename {src => tapped/src}/handle.rs (92%) rename {src => tapped/src}/lib.rs (92%) rename {src => tapped/src}/process.rs (97%) rename {src => tapped/src}/types.rs (82%) rename {tests => tapped/tests}/integration.rs (92%) diff --git a/Cargo.lock b/Cargo.lock index 6363375..7ec7ec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,31 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "abnf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a" +dependencies = [ + "abnf-core", + "nom", +] + +[[package]] +name = "abnf-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" +dependencies = [ + "nom", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +36,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -61,18 +101,73 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "async-compression" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitflags" version = "2.10.0" @@ -88,28 +183,110 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bon" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234655ec178edd82b891e262ea7cf71f6584bcd09eff94db786be23f1821825c" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ec27229c38ed0eb3c0feee3d2c1d6a4379ae44f418a29a658890e062d8f365" +dependencies = [ + "darling 0.23.0", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.114", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "cfg_aliases", +] + +[[package]] +name = "btree-range-map" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" +dependencies = [ + "btree-slab", + "cc-traits", + "range-traits", + "serde", + "slab", +] + +[[package]] +name = "btree-slab" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" +dependencies = [ + "cc-traits", + "slab", + "smallvec", +] + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "cbor4ii" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" +dependencies = [ + "serde", +] + [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "shlex", ] +[[package]] +name = "cc-traits" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" +dependencies = [ + "slab", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -122,12 +299,115 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cid" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" +dependencies = [ + "core2", + "multibase", + "multihash", + "serde", + "serde_bytes", + "unsigned-varint", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -144,6 +424,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -153,22 +442,185 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +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" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "data-encoding-macro" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +dependencies = [ + "data-encoding", + "syn 2.0.114", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "digest" version = "0.10.7" @@ -176,7 +628,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -187,14 +641,69 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] -name = "either" -version = "1.15.0" +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[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 = "env_filter" @@ -206,12 +715,6 @@ dependencies = [ "regex", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "env_logger" version = "0.11.8" @@ -225,6 +728,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -235,11 +744,37 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" @@ -273,7 +808,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -305,12 +840,13 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -340,6 +876,121 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "1.4.0" @@ -389,6 +1040,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -436,9 +1088,35 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", ] [[package]] @@ -522,6 +1200,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -543,6 +1227,58 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipld-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" +dependencies = [ + "cid", + "serde", + "serde_bytes", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -566,10 +1302,91 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "itoa" -version = "1.0.17" +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jacquard-common" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1751921e0bdae5e0077afade6161545e9ef7698306c868f800916e99ecbcaae9" +dependencies = [ + "base64", + "bon", + "bytes", + "chrono", + "cid", + "getrandom 0.2.17", + "getrandom 0.3.4", + "http", + "ipld-core", + "k256", + "langtag", + "miette", + "multibase", + "multihash", + "ouroboros", + "p256", + "postcard", + "rand", + "regex", + "regex-lite", + "reqwest", + "serde", + "serde_bytes", + "serde_html_form", + "serde_ipld_dagcbor", + "serde_json", + "signature", + "smol_str", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "trait-variant", + "url", +] + +[[package]] +name = "jacquard-derive" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d73dfee07943fdab93569ed1c28b06c6921ed891c08b415c4a323ff67e593" +dependencies = [ + "heck 0.5.0", + "jacquard-lexicon", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "jacquard-lexicon" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8411aff546569b0a1e0ef669bed2380cec1c00d48f02f3fcd57a71545321b3d8" +dependencies = [ + "cid", + "dashmap", + "heck 0.5.0", + "inventory", + "jacquard-common", + "miette", + "multihash", + "prettyplease", + "proc-macro2", + "quote", + "serde", + "serde_ipld_dagcbor", + "serde_json", + "serde_repr", + "serde_with", + "sha2", + "syn 2.0.114", + "thiserror 2.0.18", + "unicode-segmentation", +] [[package]] name = "jiff" @@ -592,7 +1409,7 @@ checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -606,16 +1423,45 @@ dependencies = [ ] [[package]] -name = "libc" -version = "0.2.180" +name = "k256" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] [[package]] -name = "linux-raw-sys" -version = "0.11.0" +name = "langtag" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600" +dependencies = [ + "serde", + "static-regular-grammar", + "thiserror 1.0.69", +] + +[[package]] +name = "lexicons-example" +version = "0.1.0" +dependencies = [ + "jacquard-common", + "jacquard-derive", + "jacquard-lexicon", + "rustversion", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "litemap" @@ -623,6 +1469,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -635,12 +1490,67 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[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" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -652,6 +1562,54 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "serde", + "unsigned-varint", +] + +[[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 = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -666,9 +1624,67 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] [[package]] name = "percent-encoding" @@ -688,6 +1704,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "portable-atomic" version = "1.13.0" @@ -703,6 +1729,19 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -712,6 +1751,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -721,15 +1766,71 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "version_check", + "yansi", +] + [[package]] name = "quinn" version = "0.11.9" @@ -744,7 +1845,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -765,7 +1866,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -787,9 +1888,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -807,7 +1908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -817,7 +1918,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -829,6 +1939,41 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "range-traits" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "regex" version = "1.12.2" @@ -852,6 +1997,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + [[package]] name = "regex-syntax" version = "0.8.8" @@ -866,7 +2017,9 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", + "encoding_rs", "futures-core", + "h2", "http", "http-body", "http-body-util", @@ -875,6 +2028,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime", "percent-encoding", "pin-project-lite", "quinn", @@ -896,6 +2050,16 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -917,16 +2081,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustix" -version = "1.1.3" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", + "semver", ] [[package]] @@ -957,9 +2117,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -967,9 +2127,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -977,24 +2137,68 @@ dependencies = [ ] [[package]] -name = "rustversion" -version = "1.0.22" +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] [[package]] -name = "ryu" -version = "1.0.22" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "schannel" -version = "0.1.28" +name = "sec1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "windows-sys 0.61.2", + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", ] [[package]] @@ -1004,7 +2208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1020,6 +2224,18 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -1030,6 +2246,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1047,7 +2273,32 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "serde_html_form" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" +dependencies = [ + "form_urlencoded", + "indexmap 2.13.0", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "serde_ipld_dagcbor" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" +dependencies = [ + "cbor4ii", + "ipld-core", + "scopeguard", + "serde", ] [[package]] @@ -1063,6 +2314,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1075,6 +2337,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1086,6 +2379,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1102,6 +2406,22 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.11" @@ -1114,6 +2434,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smol_str" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17" +dependencies = [ + "borsh", + "serde_core", +] + [[package]] name = "socket2" version = "0.6.1" @@ -1124,18 +2454,94 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "standard-site-sync" +version = "0.1.0" +dependencies = [ + "env_logger", + "jacquard-common", + "jacquard-lexicon", + "lexicons-example", + "log", + "serde", + "serde_json", + "tapped", + "tokio", +] + +[[package]] +name = "static-regular-grammar" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957" +dependencies = [ + "abnf", + "btree-range-map", + "ciborium", + "hex_fmt", + "indoc", + "proc-macro-error", + "proc-macro2", + "quote", + "serde", + "sha2", + "syn 2.0.114", + "thiserror 1.0.69", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.114" @@ -1164,47 +2570,118 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] name = "tapped" -version = "0.1.0" +version = "0.2.0" dependencies = [ "base64", - "env_logger", "futures-util", "libc", - "log", "reqwest", + "self_cell", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-tungstenite", "tracing", + "tungstenite", "url", - "which", ] [[package]] name = "thiserror" -version = "2.0.17" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -1256,7 +2733,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1271,9 +2748,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", @@ -1285,6 +2762,19 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.3" @@ -1306,13 +2796,18 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", "bitflags", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -1349,7 +2844,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1361,6 +2856,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -1369,9 +2875,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", @@ -1382,7 +2888,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 2.0.18", "utf-8", ] @@ -1398,6 +2904,24 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + [[package]] name = "untrusted" version = "0.9.0" @@ -1458,9 +2982,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -1511,7 +3035,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -1554,15 +3078,38 @@ dependencies = [ ] [[package]] -name = "which" -version = "7.0.3" +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ - "either", - "env_home", - "rustix", - "winsafe", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -1571,6 +3118,35 @@ version = "0.2.1" 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" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1727,17 +3303,11 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -1745,6 +3315,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.1" @@ -1764,7 +3340,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", "synstructure", ] @@ -1785,7 +3361,7 @@ checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1805,7 +3381,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", "synstructure", ] @@ -1845,11 +3421,11 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index 454bbe0..19a2398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,7 @@ -[package] -name = "tapped" -version = "0.1.0" -edition = "2021" -description = "Rust wrapper for the tap ATProto utility" -license = "MIT" -readme = "README.md" -authors = ["Thomas Karpiniec "] -keywords = ["atproto", "tap"] -repository = "https://github.com/thombles/tapped" - -[dependencies] -tokio = { version = "1", features = ["net", "process", "rt", "sync", "time"] } -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } -tokio-tungstenite = { version = "0.26", features = ["rustls-tls-native-roots"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -thiserror = "2" -url = { version = "2", features = ["serde"] } -futures-util = "0.3" -tracing = "0.1" -base64 = "0.22" -libc = "0.2" - -[dev-dependencies] -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -which = "7" -env_logger = "0.11" -log = "0.4" +[workspace] +members = [ + "tapped", + "lexicons-example", + "standard-site-sync", +] +resolver = "2" diff --git a/README.md b/README.md index f3b6fd4..1ce2c9b 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Add to your `Cargo.toml`: ```toml [dependencies] -tapped = "0.1" +tapped = "0.2" ``` You'll also need the `tap` binary. Build it from the [indigo repository](https://github.com/bluesky-social/indigo): @@ -32,7 +32,7 @@ cd cmd/tap && go build `tapped` has been most recently tested against: ``` -tap version v0.0.0-20260114211028-207c9d49d0de-rev-207c9d4 +tap version v0.0.0-20260120225912-12d69fa4d209-rev-12d69fa ``` ## Quick Start @@ -56,9 +56,9 @@ async fn main() -> tapped::Result<()> { while let Ok(received) = channel.recv().await { match &received.event { Event::Record(record) => { - println!("[{}] {}/{}", - record.action, - record.collection, + println!("[{:?}] {}/{}", + record.action, + record.collection, record.rkey ); } @@ -96,7 +96,7 @@ let config = TapConfig::builder() .build(); let mut process = TapProcess::spawn("/path/to/tap", config).await?; -let client = process.client(); +let client = process.client()?; // Use the client... @@ -136,8 +136,8 @@ let config = TapConfig::builder() // Network .bind("127.0.0.1:2480") - .relay_url("wss://bsky.network") - .plc_url("https://plc.directory") + .relay_url("wss://bsky.network".parse().unwrap()) + .plc_url("https://plc.directory".parse().unwrap()) // Filtering .signal_collection("app.bsky.feed.post") @@ -176,13 +176,13 @@ while let Ok(received) = channel.recv().await { Event::Record(record) => { match record.action { RecordAction::Create => { - if let Some(ref rec) = record.record { - // Access the raw JSON - println!("Type: {:?}", rec.record_type()); - - // Or deserialize to a specific type - // let post: MyPostType = rec.deserialize_as()?; + // Access the raw JSON as a string + if let Some(json) = record.record_as_str() { + println!("Raw JSON: {}", json); } + + // Or deserialize to a specific type + // let post: MyPostType = record.deserialize_as()?; } RecordAction::Update => { /* ... */ } RecordAction::Delete => { /* ... */ } @@ -229,29 +229,51 @@ println!("Outbox buffer: {}, Resync buffer: {}", outbox, resync); println!("Firehose cursor: {:?}", cursors.firehose); ``` -## Error Handling +## Example: Syncing standard.site Records with Schema Generation and Validation -All operations return `tapped::Result`, which uses the `tapped::Error` enum: +The repository includes a complete example demonstrating how to sync and validate ATProto records using `tapped` together with the [jacquard](https://crates.io/crates/jacquard) crates. -```rust -use tapped::Error; +The jacquard ecosystem provides runtime validation of records against their lexicon constraints, and the ability to generate Rust structs from lexicon JSON files. -match client.health().await { - Ok(()) => println!("Healthy!"), - Err(Error::Http(e)) => println!("HTTP error: {}", e), - Err(Error::Timeout) => println!("Request timed out"), - Err(e) => println!("Other error: {}", e), -} +``` +tapped/ +├── tapped/ # The main tapped library +├── lexicons-example/ # Generated types from lexicon schemas +│ ├── lexicons/ # Source lexicon JSON files +│ │ ├── site.standard.publication.json +│ │ ├── site.standard.document.json +│ │ └── ... +│ └── src/ # Generated Rust code +└── standard-site-sync/ # Example binary using both packages ``` -## Example: Syncing Standard Site Records - -See [examples/standard_site_sync.rs](examples/standard_site_sync.rs) for a complete example that syncs `site.standard.publication` and `site.standard.document` records to local files. +These files were generated like so: ```bash -cargo run --example standard_site_sync +# Install the code generator +cargo install jacquard-lexgen + +jacquard-codegen -i lexicons-example/lexicons -o lexicons-example/src +``` + +This produces strongly-typed structs with built-in validation. For example, the `site.standard.publication` lexicon becomes: + +```rust +use lexicons_example::site_standard::publication::Publication; + +// Deserialize from JSON +let publication: Publication = serde_json::from_str(json)?; + +// Validate against lexicon constraints (max length, grapheme limits, etc.) +publication.validate()?; + +// Access typed fields +println!("Name: {}", publication.name.as_str()); +println!("URL: {}", publication.url.as_str()); ``` +For more details see `process_record_event` [in `main.rs`](https://tangled.org/octet-stream.net/tapped/blob/main/standard-site-sync/src/main.rs). + ## License MIT diff --git a/examples/standard_site_sync.rs b/examples/standard_site_sync.rs deleted file mode 100644 index fc372cf..0000000 --- a/examples/standard_site_sync.rs +++ /dev/null @@ -1,295 +0,0 @@ -//! Example: Sync site.standard.publication and site.standard.document records. -//! -//! This example demonstrates using tapped to track publication and document -//! records from the ATProto network. It maintains an on-disk cache and -//! writes URL lists to disk on each change for inspection. -//! -//! Run with: `RUST_LOG=info cargo run --example standard_site_sync` - -use std::collections::HashMap; -use std::fs; - -use log::info; -use serde::{Deserialize, Serialize}; -use tapped::{Event, RecordAction, RecordEvent, TapConfig, TapProcess}; - -/// A site.standard.publication record (subset of fields). -#[derive(Debug, Deserialize)] -struct Publication { - url: String, - name: String, -} - -/// A site.standard.document record (subset of fields). -#[derive(Debug, Deserialize)] -struct Document { - site: String, - title: String, - #[serde(default)] - path: Option, -} - -/// Key for storing records in cache. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -struct RecordKey { - did: String, - collection: String, - rkey: String, -} - -impl RecordKey { - fn new(event: &RecordEvent) -> Self { - Self { - did: event.did.clone(), - collection: event.collection.clone(), - rkey: event.rkey.clone(), - } - } -} - -/// Cached record data. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -enum CachedRecord { - Publication { url: String }, - Document { site: String, path: Option }, -} - -fn main() -> Result<(), Box> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build()? - .block_on(async_main()) -} - -async fn async_main() -> Result<(), Box> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) - .format_timestamp_secs() - .init(); - - info!("Starting standard.site sync example"); - - let config = TapConfig::builder() - .database_url("sqlite://./tap-example.db") - .bind(":2480") - .signal_collection("site.standard.publication") - .collection_filters(vec![ - "site.standard.publication".to_string(), - "site.standard.document".to_string(), - ]) - .disable_acks(false) - .inherit_stdio(true) - .build(); - - info!("Spawning tap process..."); - - // Spawn tap (looks for ./tap first, then tap on PATH) - let process = TapProcess::spawn_default(config).await?; - info!("Tap running at {}", process.url()); - - let client = process.client()?; - client.health().await?; - info!("Tap is healthy!"); - - let mut receiver = client.channel().await?; - info!("Connected! Waiting for events..."); - - // In-memory cache - load from disk if available - let mut cache: HashMap = load_cache_from_disk(); - let (pub_count, doc_count) = count_cached(&cache); - info!( - "Loaded cache from disk: {} publications, {} documents", - pub_count, doc_count - ); - - let mut live_count = 0u64; - let mut backfill_count = 0u64; - - loop { - match receiver.recv().await { - Ok(received) => { - if let Event::Record(ref record_event) = *received { - // Track live vs backfill - if record_event.live { - live_count += 1; - } else { - backfill_count += 1; - } - - process_record_event(record_event, &mut cache); - write_output_files(&cache)?; - - // Periodically show event source breakdown - if (live_count + backfill_count).is_multiple_of(100) { - info!( - "[Stats] Live events: {}, Backfill events: {}", - live_count, backfill_count - ); - } - } else if let Event::Identity(ref identity_event) = *received { - info!( - "[IDENTITY] {} -> {} (active: {})", - identity_event.did, identity_event.handle, identity_event.is_active - ); - } - // Event is automatically acked when `received` is dropped here - } - Err(e) => { - eprintln!("Error receiving event: {}", e); - break; - } - } - } - - Ok(()) -} - -fn process_record_event(event: &RecordEvent, cache: &mut HashMap) { - let key = RecordKey::new(event); - let action_str = match event.action { - RecordAction::Create => "CREATE", - RecordAction::Update => "UPDATE", - RecordAction::Delete => "DELETE", - _ => "UNKNOWN", - }; - - // Show whether this is a live or backfill event - let source = if event.live { "LIVE" } else { "BACKFILL" }; - - info!( - "[{} {}] {} {}/{}", - action_str, source, event.did, event.collection, event.rkey - ); - - match event.action { - RecordAction::Create | RecordAction::Update => { - if let Some(ref record) = event.record { - match event.collection.as_str() { - "site.standard.publication" => match record.deserialize_as::() { - Ok(pub_record) => { - info!("Publication: {} ({})", pub_record.name, pub_record.url); - cache.insert( - key, - CachedRecord::Publication { - url: pub_record.url, - }, - ); - } - Err(e) => { - log::error!("Failed to parse publication: {}", e); - } - }, - "site.standard.document" => match record.deserialize_as::() { - Ok(doc_record) => { - let full_url = match &doc_record.path { - Some(path) if !path.is_empty() => { - format!( - "{}/{}", - doc_record.site.trim_end_matches('/'), - path.trim_start_matches('/') - ) - } - _ => doc_record.site.clone(), - }; - info!("Document: {} ({})", doc_record.title, full_url); - cache.insert( - key, - CachedRecord::Document { - site: doc_record.site, - path: doc_record.path, - }, - ); - } - Err(e) => { - log::error!("Failed to parse document: {}", e); - } - }, - _ => {} - } - } - } - RecordAction::Delete => { - cache.remove(&key); - } - _ => {} - } - - // Log cache stats - let (pub_count, doc_count) = count_cached(cache); - info!( - "Cached: {} publications, {} documents", - pub_count, doc_count - ); -} - -fn count_cached(cache: &HashMap) -> (usize, usize) { - let mut publications = 0; - let mut documents = 0; - - for record in cache.values() { - match record { - CachedRecord::Publication { .. } => publications += 1, - CachedRecord::Document { .. } => documents += 1, - } - } - - (publications, documents) -} - -fn write_output_files(cache: &HashMap) -> Result<(), std::io::Error> { - let mut publication_urls: Vec<&str> = Vec::new(); - let mut document_urls: Vec = Vec::new(); - - for record in cache.values() { - match record { - CachedRecord::Publication { url } => { - publication_urls.push(url); - } - CachedRecord::Document { site, path } => { - let full_url = match path { - Some(p) if !p.is_empty() => { - format!( - "{}/{}", - site.trim_end_matches('/'), - p.trim_start_matches('/') - ) - } - _ => site.clone(), - }; - document_urls.push(full_url); - } - } - } - - publication_urls.sort(); - document_urls.sort(); - - fs::write("publications.txt", publication_urls.join("\n"))?; - - // Write documents.txt (publication + document URLs combined) - let mut all_urls: Vec = publication_urls.iter().map(|s| s.to_string()).collect(); - all_urls.extend(document_urls); - all_urls.sort(); - all_urls.dedup(); - fs::write("documents.txt", all_urls.join("\n"))?; - - // Write cache.json for persistence - let cache_entries: Vec<(&RecordKey, &CachedRecord)> = cache.iter().collect(); - let cache_json = serde_json::to_string_pretty(&cache_entries).map_err(std::io::Error::other)?; - fs::write("cache.json", cache_json)?; - - Ok(()) -} - -fn load_cache_from_disk() -> HashMap { - match fs::read_to_string("cache.json") { - Ok(content) => match serde_json::from_str::>(&content) { - Ok(entries) => entries.into_iter().collect(), - Err(e) => { - log::warn!("Failed to parse cache.json: {}", e); - HashMap::new() - } - }, - Err(_) => HashMap::new(), - } -} diff --git a/lexicons-example/Cargo.toml b/lexicons-example/Cargo.toml new file mode 100644 index 0000000..8b609bc --- /dev/null +++ b/lexicons-example/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "lexicons-example" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Generated lexicon types for standard.site" +authors = ["Thomas Karpiniec "] +publish = false + +[dependencies] +jacquard-common = "0.9.5" +jacquard-lexicon = "0.9.5" +jacquard-derive = "0.9.5" +serde = { version = "1", features = ["derive"] } +rustversion = "1.0" +unicode-segmentation = "1.12" + +[features] +default = ["com_atproto", "site_standard"] +com_atproto = [] +site_standard = [] + +[lints.rust] +non_snake_case = "allow" + +[lints.clippy] +new_ret_no_self = "allow" +new_without_default = "allow" +type_complexity = "allow" diff --git a/lexicons-example/lexicons/com.atproto.repo.strongRef.json b/lexicons-example/lexicons/com.atproto.repo.strongRef.json new file mode 100644 index 0000000..cb79625 --- /dev/null +++ b/lexicons-example/lexicons/com.atproto.repo.strongRef.json @@ -0,0 +1,15 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.strongRef", + "description": "A URI with a content-hash fingerprint.", + "defs": { + "main": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" } + } + } + } +} diff --git a/lexicons-example/lexicons/site.standard.document.json b/lexicons-example/lexicons/site.standard.document.json new file mode 100644 index 0000000..4c808c3 --- /dev/null +++ b/lexicons-example/lexicons/site.standard.document.json @@ -0,0 +1,74 @@ +{ + "defs": { + "main": { + "key": "tid", + "record": { + "properties": { + "bskyPostRef": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "content": { + "closed": false, + "refs": [], + "type": "union" + }, + "coverImage": { + "accept": [ + "image/*" + ], + "maxSize": 1000000, + "type": "blob" + }, + "description": { + "maxGraphemes": 300, + "maxLength": 3000, + "type": "string" + }, + "path": { + "description": "combine with the publication url or the document site to construct a full url to the document", + "type": "string" + }, + "publishedAt": { + "format": "datetime", + "type": "string" + }, + "site": { + "description": "URI to the site or publication this document belongs to (https or at-uri)", + "format": "uri", + "type": "string" + }, + "tags": { + "items": { + "maxGraphemes": 50, + "maxLength": 100, + "type": "string" + }, + "type": "array" + }, + "textContent": { + "type": "string" + }, + "title": { + "maxGraphemes": 128, + "maxLength": 1280, + "type": "string" + }, + "updatedAt": { + "format": "datetime", + "type": "string" + } + }, + "required": [ + "site", + "title", + "publishedAt" + ], + "type": "object" + }, + "type": "record" + } + }, + "id": "site.standard.document", + "lexicon": 1 +} diff --git a/lexicons-example/lexicons/site.standard.graph.subscription.json b/lexicons-example/lexicons/site.standard.graph.subscription.json new file mode 100644 index 0000000..16b7d17 --- /dev/null +++ b/lexicons-example/lexicons/site.standard.graph.subscription.json @@ -0,0 +1,24 @@ +{ + "id": "site.standard.graph.subscription", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "publication" + ], + "properties": { + "publication": { + "type": "string", + "format": "at-uri", + "description": "AT-URI reference to the publication record being subscribed to (ex: at://did:plc:abc123/site.standard.publication/xyz789)." + } + } + }, + "description": "Record declaring a subscription to a publication" + } + }, + "lexicon": 1 +} diff --git a/lexicons-example/lexicons/site.standard.publication.json b/lexicons-example/lexicons/site.standard.publication.json new file mode 100644 index 0000000..fc6e3fb --- /dev/null +++ b/lexicons-example/lexicons/site.standard.publication.json @@ -0,0 +1,57 @@ +{ + "defs": { + "main": { + "key": "tid", + "record": { + "properties": { + "basicTheme": { + "ref": "site.standard.theme.basic", + "type": "ref" + }, + "description": { + "maxGraphemes": 300, + "maxLength": 3000, + "type": "string" + }, + "icon": { + "accept": [ + "image/*" + ], + "maxSize": 1000000, + "type": "blob" + }, + "name": { + "maxGraphemes": 128, + "maxLength": 1280, + "type": "string" + }, + "preferences": { + "ref": "#preferences", + "type": "ref" + }, + "url": { + "format": "uri", + "type": "string" + } + }, + "required": [ + "url", + "name" + ], + "type": "object" + }, + "type": "record" + }, + "preferences": { + "properties": { + "showInDiscover": { + "default": true, + "type": "boolean" + } + }, + "type": "object" + } + }, + "id": "site.standard.publication", + "lexicon": 1 +} diff --git a/lexicons-example/lexicons/site.standard.theme.basic.json b/lexicons-example/lexicons/site.standard.theme.basic.json new file mode 100644 index 0000000..35f4b07 --- /dev/null +++ b/lexicons-example/lexicons/site.standard.theme.basic.json @@ -0,0 +1,46 @@ +{ + "id": "site.standard.theme.basic", + "defs": { + "main": { + "type": "object", + "required": [ + "background", + "foreground", + "accent", + "accentForeground" + ], + "properties": { + "accent": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union", + "description": "Color used for links and button backgrounds." + }, + "background": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union", + "description": "Color used for content background." + }, + "foreground": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union", + "description": "Color used for content text." + }, + "accentForeground": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union", + "description": "Color used for button text." + } + }, + "description": "A simplified theme definition for publications, providing basic color customization for content display across different platforms and applications." + } + }, + "lexicon": 1 +} diff --git a/lexicons-example/lexicons/site.standard.theme.color.json b/lexicons-example/lexicons/site.standard.theme.color.json new file mode 100644 index 0000000..976efdb --- /dev/null +++ b/lexicons-example/lexicons/site.standard.theme.color.json @@ -0,0 +1,62 @@ +{ + "id": "site.standard.theme.color", + "defs": { + "rgb": { + "type": "object", + "required": [ + "r", + "g", + "b" + ], + "properties": { + "b": { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + "g": { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + "r": { + "type": "integer", + "maximum": 255, + "minimum": 0 + } + } + }, + "rgba": { + "type": "object", + "required": [ + "r", + "g", + "b", + "a" + ], + "properties": { + "a": { + "type": "integer", + "maximum": 100, + "minimum": 0 + }, + "b": { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + "g": { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + "r": { + "type": "integer", + "maximum": 255, + "minimum": 0 + } + } + } + }, + "lexicon": 1 +} diff --git a/lexicons-example/src/builder_types.rs b/lexicons-example/src/builder_types.rs new file mode 100644 index 0000000..d4b0bc4 --- /dev/null +++ b/lexicons-example/src/builder_types.rs @@ -0,0 +1,43 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +/// Marker type indicating a builder field has been set +pub struct Set(pub T); +impl Set { + /// Extract the inner value + #[inline] + pub fn into_inner(self) -> T { + self.0 + } +} + +/// Marker type indicating a builder field has not been set +pub struct Unset; +/// Trait indicating a builder field is set (has a value) +#[rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the field `{Self}` was not set, but this method requires it to be set", + label = "the field `{Self}` was not set" + ) +)] +pub trait IsSet: private::Sealed {} +/// Trait indicating a builder field is unset (no value yet) +#[rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the field `{Self}` was already set, but this method requires it to be unset", + label = "the field `{Self}` was already set" + ) +)] +pub trait IsUnset: private::Sealed {} +impl IsSet for Set {} +impl IsUnset for Unset {} +mod private { + /// Sealed trait to prevent external implementations + pub trait Sealed {} + impl Sealed for super::Set {} + impl Sealed for super::Unset {} +} diff --git a/lexicons-example/src/com_atproto.rs b/lexicons-example/src/com_atproto.rs new file mode 100644 index 0000000..80674cd --- /dev/null +++ b/lexicons-example/src/com_atproto.rs @@ -0,0 +1,6 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod repo; diff --git a/lexicons-example/src/com_atproto/repo.rs b/lexicons-example/src/com_atproto/repo.rs new file mode 100644 index 0000000..1d58de6 --- /dev/null +++ b/lexicons-example/src/com_atproto/repo.rs @@ -0,0 +1,6 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod strong_ref; diff --git a/lexicons-example/src/com_atproto/repo/strong_ref.rs b/lexicons-example/src/com_atproto/repo/strong_ref.rs new file mode 100644 index 0000000..bf2a4c4 --- /dev/null +++ b/lexicons-example/src/com_atproto/repo/strong_ref.rs @@ -0,0 +1,244 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: com.atproto.repo.strongRef +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct StrongRef<'a> { + #[serde(borrow)] + pub cid: jacquard_common::types::string::Cid<'a>, + #[serde(borrow)] + pub uri: jacquard_common::types::string::AtUri<'a>, +} + +pub mod strong_ref_state { + + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type Uri; + type Cid; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Uri = Unset; + type Cid = Unset; + } + ///State transition - sets the `uri` field to Set + pub struct SetUri(PhantomData S>); + impl sealed::Sealed for SetUri {} + impl State for SetUri { + type Uri = Set; + type Cid = S::Cid; + } + ///State transition - sets the `cid` field to Set + pub struct SetCid(PhantomData S>); + impl sealed::Sealed for SetCid {} + impl State for SetCid { + type Uri = S::Uri; + type Cid = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `uri` field + pub struct uri(()); + ///Marker type for the `cid` field + pub struct cid(()); + } +} + +/// Builder for constructing an instance of this type +pub struct StrongRefBuilder<'a, S: strong_ref_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option>, + ::core::option::Option>, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> StrongRef<'a> { + /// Create a new builder for this type + pub fn new() -> StrongRefBuilder<'a, strong_ref_state::Empty> { + StrongRefBuilder::new() + } +} + +impl<'a> StrongRefBuilder<'a, strong_ref_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + StrongRefBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None, None), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> StrongRefBuilder<'a, S> +where + S: strong_ref_state::State, + S::Cid: strong_ref_state::IsUnset, +{ + /// Set the `cid` field (required) + pub fn cid( + mut self, + value: impl Into>, + ) -> StrongRefBuilder<'a, strong_ref_state::SetCid> { + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); + StrongRefBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> StrongRefBuilder<'a, S> +where + S: strong_ref_state::State, + S::Uri: strong_ref_state::IsUnset, +{ + /// Set the `uri` field (required) + pub fn uri( + mut self, + value: impl Into>, + ) -> StrongRefBuilder<'a, strong_ref_state::SetUri> { + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); + StrongRefBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> StrongRefBuilder<'a, S> +where + S: strong_ref_state::State, + S::Uri: strong_ref_state::IsSet, + S::Cid: strong_ref_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> StrongRef<'a> { + StrongRef { + cid: self.__unsafe_private_named.0.unwrap(), + uri: self.__unsafe_private_named.1.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> StrongRef<'a> { + StrongRef { + cid: self.__unsafe_private_named.0.unwrap(), + uri: self.__unsafe_private_named.1.unwrap(), + extra_data: Some(extra_data), + } + } +} + +fn lexicon_doc_com_atproto_repo_strongRef() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + ::jacquard_lexicon::lexicon::LexiconDoc { + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, + id: ::jacquard_common::CowStr::new_static("com.atproto.repo.strongRef"), + revision: None, + description: None, + defs: { + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("main"), + ::jacquard_lexicon::lexicon::LexUserType::Object( + ::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some(vec![ + ::jacquard_common::smol_str::SmolStr::new_static("uri"), + ::jacquard_common::smol_str::SmolStr::new_static("cid"), + ]), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("cid"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String( + ::jacquard_lexicon::lexicon::LexString { + description: None, + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::Cid, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }, + ), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("uri"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String( + ::jacquard_lexicon::lexicon::LexString { + description: None, + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::AtUri, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }, + ), + ); + map + }, + }, + ), + ); + map + }, + } +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for StrongRef<'a> { + fn nsid() -> &'static str { + "com.atproto.repo.strongRef" + } + fn def_name() -> &'static str { + "main" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_com_atproto_repo_strongRef() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} diff --git a/lexicons-example/src/lib.rs b/lexicons-example/src/lib.rs new file mode 100644 index 0000000..7b63d74 --- /dev/null +++ b/lexicons-example/src/lib.rs @@ -0,0 +1,12 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod builder_types; + +#[cfg(feature = "com_atproto")] +pub mod com_atproto; + +#[cfg(feature = "site_standard")] +pub mod site_standard; diff --git a/lexicons-example/src/site_standard.rs b/lexicons-example/src/site_standard.rs new file mode 100644 index 0000000..ddb6898 --- /dev/null +++ b/lexicons-example/src/site_standard.rs @@ -0,0 +1,9 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod document; +pub mod graph; +pub mod publication; +pub mod theme; diff --git a/lexicons-example/src/site_standard/document.rs b/lexicons-example/src/site_standard/document.rs new file mode 100644 index 0000000..6948ead --- /dev/null +++ b/lexicons-example/src/site_standard/document.rs @@ -0,0 +1,714 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: site.standard.document +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct Document<'a> { + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub bsky_post_ref: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub content: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cover_image: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub description: std::option::Option>, + /// combine with the publication url or the document site to construct a full url to the document + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub path: std::option::Option>, + pub published_at: jacquard_common::types::string::Datetime, + /// URI to the site or publication this document belongs to (https or at-uri) + #[serde(borrow)] + pub site: jacquard_common::types::string::Uri<'a>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub tags: std::option::Option>>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub text_content: std::option::Option>, + #[serde(borrow)] + pub title: jacquard_common::CowStr<'a>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub updated_at: std::option::Option, +} + +pub mod document_state { + + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type Title; + type Site; + type PublishedAt; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Title = Unset; + type Site = Unset; + type PublishedAt = Unset; + } + ///State transition - sets the `title` field to Set + pub struct SetTitle(PhantomData S>); + impl sealed::Sealed for SetTitle {} + impl State for SetTitle { + type Title = Set; + type Site = S::Site; + type PublishedAt = S::PublishedAt; + } + ///State transition - sets the `site` field to Set + pub struct SetSite(PhantomData S>); + impl sealed::Sealed for SetSite {} + impl State for SetSite { + type Title = S::Title; + type Site = Set; + type PublishedAt = S::PublishedAt; + } + ///State transition - sets the `published_at` field to Set + pub struct SetPublishedAt(PhantomData S>); + impl sealed::Sealed for SetPublishedAt {} + impl State for SetPublishedAt { + type Title = S::Title; + type Site = S::Site; + type PublishedAt = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `title` field + pub struct title(()); + ///Marker type for the `site` field + pub struct site(()); + ///Marker type for the `published_at` field + pub struct published_at(()); + } +} + +/// Builder for constructing an instance of this type +pub struct DocumentBuilder<'a, S: document_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option, + ::core::option::Option>, + ::core::option::Option>>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> Document<'a> { + /// Create a new builder for this type + pub fn new() -> DocumentBuilder<'a, document_state::Empty> { + DocumentBuilder::new() + } +} + +impl<'a> DocumentBuilder<'a, document_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + DocumentBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: ( + None, None, None, None, None, None, None, None, None, None, None, + ), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `bskyPostRef` field (optional) + pub fn bsky_post_ref( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.0 = value.into(); + self + } + /// Set the `bskyPostRef` field to an Option value (optional) + pub fn maybe_bsky_post_ref( + mut self, + value: Option>, + ) -> Self { + self.__unsafe_private_named.0 = value; + self + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `content` field (optional) + pub fn content( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.1 = value.into(); + self + } + /// Set the `content` field to an Option value (optional) + pub fn maybe_content(mut self, value: Option>) -> Self { + self.__unsafe_private_named.1 = value; + self + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `coverImage` field (optional) + pub fn cover_image( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.2 = value.into(); + self + } + /// Set the `coverImage` field to an Option value (optional) + pub fn maybe_cover_image( + mut self, + value: Option>, + ) -> Self { + self.__unsafe_private_named.2 = value; + self + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `description` field (optional) + pub fn description(mut self, value: impl Into>>) -> Self { + self.__unsafe_private_named.3 = value.into(); + self + } + /// Set the `description` field to an Option value (optional) + pub fn maybe_description(mut self, value: Option>) -> Self { + self.__unsafe_private_named.3 = value; + self + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `path` field (optional) + pub fn path(mut self, value: impl Into>>) -> Self { + self.__unsafe_private_named.4 = value.into(); + self + } + /// Set the `path` field to an Option value (optional) + pub fn maybe_path(mut self, value: Option>) -> Self { + self.__unsafe_private_named.4 = value; + self + } +} + +impl<'a, S> DocumentBuilder<'a, S> +where + S: document_state::State, + S::PublishedAt: document_state::IsUnset, +{ + /// Set the `publishedAt` field (required) + pub fn published_at( + mut self, + value: impl Into, + ) -> DocumentBuilder<'a, document_state::SetPublishedAt> { + self.__unsafe_private_named.5 = ::core::option::Option::Some(value.into()); + DocumentBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> DocumentBuilder<'a, S> +where + S: document_state::State, + S::Site: document_state::IsUnset, +{ + /// Set the `site` field (required) + pub fn site( + mut self, + value: impl Into>, + ) -> DocumentBuilder<'a, document_state::SetSite> { + self.__unsafe_private_named.6 = ::core::option::Option::Some(value.into()); + DocumentBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `tags` field (optional) + pub fn tags(mut self, value: impl Into>>>) -> Self { + self.__unsafe_private_named.7 = value.into(); + self + } + /// Set the `tags` field to an Option value (optional) + pub fn maybe_tags(mut self, value: Option>>) -> Self { + self.__unsafe_private_named.7 = value; + self + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `textContent` field (optional) + pub fn text_content(mut self, value: impl Into>>) -> Self { + self.__unsafe_private_named.8 = value.into(); + self + } + /// Set the `textContent` field to an Option value (optional) + pub fn maybe_text_content(mut self, value: Option>) -> Self { + self.__unsafe_private_named.8 = value; + self + } +} + +impl<'a, S> DocumentBuilder<'a, S> +where + S: document_state::State, + S::Title: document_state::IsUnset, +{ + /// Set the `title` field (required) + pub fn title( + mut self, + value: impl Into>, + ) -> DocumentBuilder<'a, document_state::SetTitle> { + self.__unsafe_private_named.9 = ::core::option::Option::Some(value.into()); + DocumentBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S: document_state::State> DocumentBuilder<'a, S> { + /// Set the `updatedAt` field (optional) + pub fn updated_at( + mut self, + value: impl Into>, + ) -> Self { + self.__unsafe_private_named.10 = value.into(); + self + } + /// Set the `updatedAt` field to an Option value (optional) + pub fn maybe_updated_at( + mut self, + value: Option, + ) -> Self { + self.__unsafe_private_named.10 = value; + self + } +} + +impl<'a, S> DocumentBuilder<'a, S> +where + S: document_state::State, + S::Title: document_state::IsSet, + S::Site: document_state::IsSet, + S::PublishedAt: document_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> Document<'a> { + Document { + bsky_post_ref: self.__unsafe_private_named.0, + content: self.__unsafe_private_named.1, + cover_image: self.__unsafe_private_named.2, + description: self.__unsafe_private_named.3, + path: self.__unsafe_private_named.4, + published_at: self.__unsafe_private_named.5.unwrap(), + site: self.__unsafe_private_named.6.unwrap(), + tags: self.__unsafe_private_named.7, + text_content: self.__unsafe_private_named.8, + title: self.__unsafe_private_named.9.unwrap(), + updated_at: self.__unsafe_private_named.10, + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> Document<'a> { + Document { + bsky_post_ref: self.__unsafe_private_named.0, + content: self.__unsafe_private_named.1, + cover_image: self.__unsafe_private_named.2, + description: self.__unsafe_private_named.3, + path: self.__unsafe_private_named.4, + published_at: self.__unsafe_private_named.5.unwrap(), + site: self.__unsafe_private_named.6.unwrap(), + tags: self.__unsafe_private_named.7, + text_content: self.__unsafe_private_named.8, + title: self.__unsafe_private_named.9.unwrap(), + updated_at: self.__unsafe_private_named.10, + extra_data: Some(extra_data), + } + } +} + +impl<'a> Document<'a> { + pub fn uri( + uri: impl Into>, + ) -> Result< + jacquard_common::types::uri::RecordUri<'a, DocumentRecord>, + jacquard_common::types::uri::UriError, + > { + jacquard_common::types::uri::RecordUri::try_from_uri( + jacquard_common::types::string::AtUri::new_cow(uri.into())?, + ) + } +} + +/// Typed wrapper for GetRecord response with this collection's record type. +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct DocumentGetRecordOutput<'a> { + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cid: std::option::Option>, + #[serde(borrow)] + pub uri: jacquard_common::types::string::AtUri<'a>, + #[serde(borrow)] + pub value: Document<'a>, +} + +impl From> for Document<'_> { + fn from(output: DocumentGetRecordOutput<'_>) -> Self { + use jacquard_common::IntoStatic; + output.value.into_static() + } +} + +impl jacquard_common::types::collection::Collection for Document<'_> { + const NSID: &'static str = "site.standard.document"; + type Record = DocumentRecord; +} + +/// Marker type for deserializing records from this collection. +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct DocumentRecord; +impl jacquard_common::xrpc::XrpcResp for DocumentRecord { + const NSID: &'static str = "site.standard.document"; + const ENCODING: &'static str = "application/json"; + type Output<'de> = DocumentGetRecordOutput<'de>; + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; +} + +impl jacquard_common::types::collection::Collection for DocumentRecord { + const NSID: &'static str = "site.standard.document"; + type Record = DocumentRecord; +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Document<'a> { + fn nsid() -> &'static str { + "site.standard.document" + } + fn def_name() -> &'static str { + "main" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_site_standard_document() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + if let Some(ref value) = self.description { + #[allow(unused_comparisons)] + if ::len(value.as_ref()) > 3000usize { + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("description"), + max: 3000usize, + actual: ::len(value.as_ref()), + }); + } + } + if let Some(ref value) = self.description { + { + let count = + ::unicode_segmentation::UnicodeSegmentation::graphemes(value.as_ref(), true) + .count(); + if count > 300usize { + return Err( + ::jacquard_lexicon::validation::ConstraintError::MaxGraphemes { + path: ::jacquard_lexicon::validation::ValidationPath::from_field( + "description", + ), + max: 300usize, + actual: count, + }, + ); + } + } + } + { + let value = &self.title; + #[allow(unused_comparisons)] + if ::len(value.as_ref()) > 1280usize { + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("title"), + max: 1280usize, + actual: ::len(value.as_ref()), + }); + } + } + { + let value = &self.title; + { + let count = + ::unicode_segmentation::UnicodeSegmentation::graphemes(value.as_ref(), true) + .count(); + if count > 128usize { + return Err( + ::jacquard_lexicon::validation::ConstraintError::MaxGraphemes { + path: ::jacquard_lexicon::validation::ValidationPath::from_field( + "title", + ), + max: 128usize, + actual: count, + }, + ); + } + } + } + Ok(()) + } +} + +fn lexicon_doc_site_standard_document() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + ::jacquard_lexicon::lexicon::LexiconDoc { + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, + id: ::jacquard_common::CowStr::new_static("site.standard.document"), + revision: None, + description: None, + defs: { + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("main"), + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { + description: None, + key: Some(::jacquard_common::CowStr::new_static("tid")), + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("site"), + ::jacquard_common::smol_str::SmolStr::new_static("title"), + ::jacquard_common::smol_str::SmolStr::new_static("publishedAt") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "bskyPostRef", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { + description: None, + r#ref: ::jacquard_common::CowStr::new_static( + "com.atproto.repo.strongRef", + ), + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("content"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { + description: None, + refs: vec![], + closed: Some(false), + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "coverImage", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { + description: None, + accept: None, + max_size: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "description", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: Some(3000usize), + min_graphemes: None, + max_graphemes: Some(300usize), + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("path"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: Some( + ::jacquard_common::CowStr::new_static( + "combine with the publication url or the document site to construct a full url to the document", + ), + ), + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "publishedAt", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("site"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: Some( + ::jacquard_common::CowStr::new_static( + "URI to the site or publication this document belongs to (https or at-uri)", + ), + ), + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::Uri, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("tags"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { + description: None, + items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: Some(100usize), + min_graphemes: None, + max_graphemes: Some(50usize), + r#enum: None, + r#const: None, + known_values: None, + }), + min_length: None, + max_length: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "textContent", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("title"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: Some(1280usize), + min_graphemes: None, + max_graphemes: Some(128usize), + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "updatedAt", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map + }, + }), + }), + ); + map + }, + } +} diff --git a/lexicons-example/src/site_standard/graph.rs b/lexicons-example/src/site_standard/graph.rs new file mode 100644 index 0000000..f6dd500 --- /dev/null +++ b/lexicons-example/src/site_standard/graph.rs @@ -0,0 +1,6 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod subscription; diff --git a/lexicons-example/src/site_standard/graph/subscription.rs b/lexicons-example/src/site_standard/graph/subscription.rs new file mode 100644 index 0000000..caf87ef --- /dev/null +++ b/lexicons-example/src/site_standard/graph/subscription.rs @@ -0,0 +1,255 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: site.standard.graph.subscription +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +/// Record declaring a subscription to a publication +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct Subscription<'a> { + /// AT-URI reference to the publication record being subscribed to (ex: at://did:plc:abc123/site.standard.publication/xyz789). + #[serde(borrow)] + pub publication: jacquard_common::types::string::AtUri<'a>, +} + +pub mod subscription_state { + + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type Publication; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Publication = Unset; + } + ///State transition - sets the `publication` field to Set + pub struct SetPublication(PhantomData S>); + impl sealed::Sealed for SetPublication {} + impl State for SetPublication { + type Publication = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `publication` field + pub struct publication(()); + } +} + +/// Builder for constructing an instance of this type +pub struct SubscriptionBuilder<'a, S: subscription_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: (::core::option::Option>,), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> Subscription<'a> { + /// Create a new builder for this type + pub fn new() -> SubscriptionBuilder<'a, subscription_state::Empty> { + SubscriptionBuilder::new() + } +} + +impl<'a> SubscriptionBuilder<'a, subscription_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + SubscriptionBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None,), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> SubscriptionBuilder<'a, S> +where + S: subscription_state::State, + S::Publication: subscription_state::IsUnset, +{ + /// Set the `publication` field (required) + pub fn publication( + mut self, + value: impl Into>, + ) -> SubscriptionBuilder<'a, subscription_state::SetPublication> { + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); + SubscriptionBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> SubscriptionBuilder<'a, S> +where + S: subscription_state::State, + S::Publication: subscription_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> Subscription<'a> { + Subscription { + publication: self.__unsafe_private_named.0.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> Subscription<'a> { + Subscription { + publication: self.__unsafe_private_named.0.unwrap(), + extra_data: Some(extra_data), + } + } +} + +impl<'a> Subscription<'a> { + pub fn uri( + uri: impl Into>, + ) -> Result< + jacquard_common::types::uri::RecordUri<'a, SubscriptionRecord>, + jacquard_common::types::uri::UriError, + > { + jacquard_common::types::uri::RecordUri::try_from_uri( + jacquard_common::types::string::AtUri::new_cow(uri.into())?, + ) + } +} + +/// Typed wrapper for GetRecord response with this collection's record type. +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct SubscriptionGetRecordOutput<'a> { + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cid: std::option::Option>, + #[serde(borrow)] + pub uri: jacquard_common::types::string::AtUri<'a>, + #[serde(borrow)] + pub value: Subscription<'a>, +} + +impl From> for Subscription<'_> { + fn from(output: SubscriptionGetRecordOutput<'_>) -> Self { + use jacquard_common::IntoStatic; + output.value.into_static() + } +} + +impl jacquard_common::types::collection::Collection for Subscription<'_> { + const NSID: &'static str = "site.standard.graph.subscription"; + type Record = SubscriptionRecord; +} + +/// Marker type for deserializing records from this collection. +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct SubscriptionRecord; +impl jacquard_common::xrpc::XrpcResp for SubscriptionRecord { + const NSID: &'static str = "site.standard.graph.subscription"; + const ENCODING: &'static str = "application/json"; + type Output<'de> = SubscriptionGetRecordOutput<'de>; + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; +} + +impl jacquard_common::types::collection::Collection for SubscriptionRecord { + const NSID: &'static str = "site.standard.graph.subscription"; + type Record = SubscriptionRecord; +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subscription<'a> { + fn nsid() -> &'static str { + "site.standard.graph.subscription" + } + fn def_name() -> &'static str { + "main" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_site_standard_graph_subscription() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} + +fn lexicon_doc_site_standard_graph_subscription() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> +{ + ::jacquard_lexicon::lexicon::LexiconDoc { + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, + id: ::jacquard_common::CowStr::new_static("site.standard.graph.subscription"), + revision: None, + description: None, + defs: { + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("main"), + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { + description: Some( + ::jacquard_common::CowStr::new_static( + "Record declaring a subscription to a publication", + ), + ), + key: Some(::jacquard_common::CowStr::new_static("tid")), + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("publication") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "publication", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: Some( + ::jacquard_common::CowStr::new_static( + "AT-URI reference to the publication record being subscribed to (ex: at://did:plc:abc123/site.standard.publication/xyz789).", + ), + ), + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::AtUri, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map + }, + }), + }), + ); + map + }, + } +} diff --git a/lexicons-example/src/site_standard/publication.rs b/lexicons-example/src/site_standard/publication.rs new file mode 100644 index 0000000..ced9e99 --- /dev/null +++ b/lexicons-example/src/site_standard/publication.rs @@ -0,0 +1,550 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: site.standard.publication +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct Publication<'a> { + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub basic_theme: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub description: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub icon: std::option::Option>, + #[serde(borrow)] + pub name: jacquard_common::CowStr<'a>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub preferences: std::option::Option>, + #[serde(borrow)] + pub url: jacquard_common::types::string::Uri<'a>, +} + +pub mod publication_state { + + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type Name; + type Url; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Name = Unset; + type Url = Unset; + } + ///State transition - sets the `name` field to Set + pub struct SetName(PhantomData S>); + impl sealed::Sealed for SetName {} + impl State for SetName { + type Name = Set; + type Url = S::Url; + } + ///State transition - sets the `url` field to Set + pub struct SetUrl(PhantomData S>); + impl sealed::Sealed for SetUrl {} + impl State for SetUrl { + type Name = S::Name; + type Url = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `name` field + pub struct name(()); + ///Marker type for the `url` field + pub struct url(()); + } +} + +/// Builder for constructing an instance of this type +pub struct PublicationBuilder<'a, S: publication_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> Publication<'a> { + /// Create a new builder for this type + pub fn new() -> PublicationBuilder<'a, publication_state::Empty> { + PublicationBuilder::new() + } +} + +impl<'a> PublicationBuilder<'a, publication_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + PublicationBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None, None, None, None, None, None), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S: publication_state::State> PublicationBuilder<'a, S> { + /// Set the `basicTheme` field (optional) + pub fn basic_theme( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.0 = value.into(); + self + } + /// Set the `basicTheme` field to an Option value (optional) + pub fn maybe_basic_theme( + mut self, + value: Option>, + ) -> Self { + self.__unsafe_private_named.0 = value; + self + } +} + +impl<'a, S: publication_state::State> PublicationBuilder<'a, S> { + /// Set the `description` field (optional) + pub fn description(mut self, value: impl Into>>) -> Self { + self.__unsafe_private_named.1 = value.into(); + self + } + /// Set the `description` field to an Option value (optional) + pub fn maybe_description(mut self, value: Option>) -> Self { + self.__unsafe_private_named.1 = value; + self + } +} + +impl<'a, S: publication_state::State> PublicationBuilder<'a, S> { + /// Set the `icon` field (optional) + pub fn icon( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.2 = value.into(); + self + } + /// Set the `icon` field to an Option value (optional) + pub fn maybe_icon(mut self, value: Option>) -> Self { + self.__unsafe_private_named.2 = value; + self + } +} + +impl<'a, S> PublicationBuilder<'a, S> +where + S: publication_state::State, + S::Name: publication_state::IsUnset, +{ + /// Set the `name` field (required) + pub fn name( + mut self, + value: impl Into>, + ) -> PublicationBuilder<'a, publication_state::SetName> { + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); + PublicationBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S: publication_state::State> PublicationBuilder<'a, S> { + /// Set the `preferences` field (optional) + pub fn preferences( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.4 = value.into(); + self + } + /// Set the `preferences` field to an Option value (optional) + pub fn maybe_preferences( + mut self, + value: Option>, + ) -> Self { + self.__unsafe_private_named.4 = value; + self + } +} + +impl<'a, S> PublicationBuilder<'a, S> +where + S: publication_state::State, + S::Url: publication_state::IsUnset, +{ + /// Set the `url` field (required) + pub fn url( + mut self, + value: impl Into>, + ) -> PublicationBuilder<'a, publication_state::SetUrl> { + self.__unsafe_private_named.5 = ::core::option::Option::Some(value.into()); + PublicationBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> PublicationBuilder<'a, S> +where + S: publication_state::State, + S::Name: publication_state::IsSet, + S::Url: publication_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> Publication<'a> { + Publication { + basic_theme: self.__unsafe_private_named.0, + description: self.__unsafe_private_named.1, + icon: self.__unsafe_private_named.2, + name: self.__unsafe_private_named.3.unwrap(), + preferences: self.__unsafe_private_named.4, + url: self.__unsafe_private_named.5.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> Publication<'a> { + Publication { + basic_theme: self.__unsafe_private_named.0, + description: self.__unsafe_private_named.1, + icon: self.__unsafe_private_named.2, + name: self.__unsafe_private_named.3.unwrap(), + preferences: self.__unsafe_private_named.4, + url: self.__unsafe_private_named.5.unwrap(), + extra_data: Some(extra_data), + } + } +} + +impl<'a> Publication<'a> { + pub fn uri( + uri: impl Into>, + ) -> Result< + jacquard_common::types::uri::RecordUri<'a, PublicationRecord>, + jacquard_common::types::uri::UriError, + > { + jacquard_common::types::uri::RecordUri::try_from_uri( + jacquard_common::types::string::AtUri::new_cow(uri.into())?, + ) + } +} + +/// Typed wrapper for GetRecord response with this collection's record type. +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct PublicationGetRecordOutput<'a> { + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cid: std::option::Option>, + #[serde(borrow)] + pub uri: jacquard_common::types::string::AtUri<'a>, + #[serde(borrow)] + pub value: Publication<'a>, +} + +impl From> for Publication<'_> { + fn from(output: PublicationGetRecordOutput<'_>) -> Self { + use jacquard_common::IntoStatic; + output.value.into_static() + } +} + +impl jacquard_common::types::collection::Collection for Publication<'_> { + const NSID: &'static str = "site.standard.publication"; + type Record = PublicationRecord; +} + +/// Marker type for deserializing records from this collection. +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct PublicationRecord; +impl jacquard_common::xrpc::XrpcResp for PublicationRecord { + const NSID: &'static str = "site.standard.publication"; + const ENCODING: &'static str = "application/json"; + type Output<'de> = PublicationGetRecordOutput<'de>; + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; +} + +impl jacquard_common::types::collection::Collection for PublicationRecord { + const NSID: &'static str = "site.standard.publication"; + type Record = PublicationRecord; +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Publication<'a> { + fn nsid() -> &'static str { + "site.standard.publication" + } + fn def_name() -> &'static str { + "main" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_site_standard_publication() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + if let Some(ref value) = self.description { + #[allow(unused_comparisons)] + if ::len(value.as_ref()) > 3000usize { + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("description"), + max: 3000usize, + actual: ::len(value.as_ref()), + }); + } + } + if let Some(ref value) = self.description { + { + let count = + ::unicode_segmentation::UnicodeSegmentation::graphemes(value.as_ref(), true) + .count(); + if count > 300usize { + return Err( + ::jacquard_lexicon::validation::ConstraintError::MaxGraphemes { + path: ::jacquard_lexicon::validation::ValidationPath::from_field( + "description", + ), + max: 300usize, + actual: count, + }, + ); + } + } + } + { + let value = &self.name; + #[allow(unused_comparisons)] + if ::len(value.as_ref()) > 1280usize { + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("name"), + max: 1280usize, + actual: ::len(value.as_ref()), + }); + } + } + { + let value = &self.name; + { + let count = + ::unicode_segmentation::UnicodeSegmentation::graphemes(value.as_ref(), true) + .count(); + if count > 128usize { + return Err( + ::jacquard_lexicon::validation::ConstraintError::MaxGraphemes { + path: ::jacquard_lexicon::validation::ValidationPath::from_field( + "name", + ), + max: 128usize, + actual: count, + }, + ); + } + } + } + Ok(()) + } +} + +fn lexicon_doc_site_standard_publication() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + ::jacquard_lexicon::lexicon::LexiconDoc { + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, + id: ::jacquard_common::CowStr::new_static("site.standard.publication"), + revision: None, + description: None, + defs: { + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("main"), + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { + description: None, + key: Some(::jacquard_common::CowStr::new_static("tid")), + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("url"), + ::jacquard_common::smol_str::SmolStr::new_static("name") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "basicTheme", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { + description: None, + r#ref: ::jacquard_common::CowStr::new_static( + "site.standard.theme.basic", + ), + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "description", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: Some(3000usize), + min_graphemes: None, + max_graphemes: Some(300usize), + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("icon"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { + description: None, + accept: None, + max_size: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("name"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: Some(1280usize), + min_graphemes: None, + max_graphemes: Some(128usize), + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "preferences", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { + description: None, + r#ref: ::jacquard_common::CowStr::new_static("#preferences"), + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("url"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::Uri, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map + }, + }), + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("preferences"), + ::jacquard_lexicon::lexicon::LexUserType::Object( + ::jacquard_lexicon::lexicon::LexObject { + description: None, + required: None, + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("showInDiscover"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean( + ::jacquard_lexicon::lexicon::LexBoolean { + description: None, + default: None, + r#const: None, + }, + ), + ); + map + }, + }, + ), + ); + map + }, + } +} + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic, + Default, +)] +#[serde(rename_all = "camelCase")] +pub struct Preferences<'a> { + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub show_in_discover: std::option::Option, +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Preferences<'a> { + fn nsid() -> &'static str { + "site.standard.publication" + } + fn def_name() -> &'static str { + "preferences" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_site_standard_publication() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} diff --git a/lexicons-example/src/site_standard/theme.rs b/lexicons-example/src/site_standard/theme.rs new file mode 100644 index 0000000..bee0804 --- /dev/null +++ b/lexicons-example/src/site_standard/theme.rs @@ -0,0 +1,7 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod basic; +pub mod color; diff --git a/lexicons-example/src/site_standard/theme/basic.rs b/lexicons-example/src/site_standard/theme/basic.rs new file mode 100644 index 0000000..83551a9 --- /dev/null +++ b/lexicons-example/src/site_standard/theme/basic.rs @@ -0,0 +1,359 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: site.standard.theme.basic +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +/// A simplified theme definition for publications, providing basic color customization for content display across different platforms and applications. +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct Basic<'a> { + /// Color used for links and button backgrounds. + #[serde(borrow)] + pub accent: crate::site_standard::theme::color::Rgb<'a>, + /// Color used for button text. + #[serde(borrow)] + pub accent_foreground: crate::site_standard::theme::color::Rgb<'a>, + /// Color used for content background. + #[serde(borrow)] + pub background: crate::site_standard::theme::color::Rgb<'a>, + /// Color used for content text. + #[serde(borrow)] + pub foreground: crate::site_standard::theme::color::Rgb<'a>, +} + +pub mod basic_state { + + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type Background; + type AccentForeground; + type Accent; + type Foreground; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Background = Unset; + type AccentForeground = Unset; + type Accent = Unset; + type Foreground = Unset; + } + ///State transition - sets the `background` field to Set + pub struct SetBackground(PhantomData S>); + impl sealed::Sealed for SetBackground {} + impl State for SetBackground { + type Background = Set; + type AccentForeground = S::AccentForeground; + type Accent = S::Accent; + type Foreground = S::Foreground; + } + ///State transition - sets the `accent_foreground` field to Set + pub struct SetAccentForeground(PhantomData S>); + impl sealed::Sealed for SetAccentForeground {} + impl State for SetAccentForeground { + type Background = S::Background; + type AccentForeground = Set; + type Accent = S::Accent; + type Foreground = S::Foreground; + } + ///State transition - sets the `accent` field to Set + pub struct SetAccent(PhantomData S>); + impl sealed::Sealed for SetAccent {} + impl State for SetAccent { + type Background = S::Background; + type AccentForeground = S::AccentForeground; + type Accent = Set; + type Foreground = S::Foreground; + } + ///State transition - sets the `foreground` field to Set + pub struct SetForeground(PhantomData S>); + impl sealed::Sealed for SetForeground {} + impl State for SetForeground { + type Background = S::Background; + type AccentForeground = S::AccentForeground; + type Accent = S::Accent; + type Foreground = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `background` field + pub struct background(()); + ///Marker type for the `accent_foreground` field + pub struct accent_foreground(()); + ///Marker type for the `accent` field + pub struct accent(()); + ///Marker type for the `foreground` field + pub struct foreground(()); + } +} + +/// Builder for constructing an instance of this type +pub struct BasicBuilder<'a, S: basic_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> Basic<'a> { + /// Create a new builder for this type + pub fn new() -> BasicBuilder<'a, basic_state::Empty> { + BasicBuilder::new() + } +} + +impl<'a> BasicBuilder<'a, basic_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + BasicBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None, None, None, None), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> BasicBuilder<'a, S> +where + S: basic_state::State, + S::Accent: basic_state::IsUnset, +{ + /// Set the `accent` field (required) + pub fn accent( + mut self, + value: impl Into>, + ) -> BasicBuilder<'a, basic_state::SetAccent> { + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); + BasicBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> BasicBuilder<'a, S> +where + S: basic_state::State, + S::AccentForeground: basic_state::IsUnset, +{ + /// Set the `accentForeground` field (required) + pub fn accent_foreground( + mut self, + value: impl Into>, + ) -> BasicBuilder<'a, basic_state::SetAccentForeground> { + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); + BasicBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> BasicBuilder<'a, S> +where + S: basic_state::State, + S::Background: basic_state::IsUnset, +{ + /// Set the `background` field (required) + pub fn background( + mut self, + value: impl Into>, + ) -> BasicBuilder<'a, basic_state::SetBackground> { + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); + BasicBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> BasicBuilder<'a, S> +where + S: basic_state::State, + S::Foreground: basic_state::IsUnset, +{ + /// Set the `foreground` field (required) + pub fn foreground( + mut self, + value: impl Into>, + ) -> BasicBuilder<'a, basic_state::SetForeground> { + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); + BasicBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> BasicBuilder<'a, S> +where + S: basic_state::State, + S::Background: basic_state::IsSet, + S::AccentForeground: basic_state::IsSet, + S::Accent: basic_state::IsSet, + S::Foreground: basic_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> Basic<'a> { + Basic { + accent: self.__unsafe_private_named.0.unwrap(), + accent_foreground: self.__unsafe_private_named.1.unwrap(), + background: self.__unsafe_private_named.2.unwrap(), + foreground: self.__unsafe_private_named.3.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> Basic<'a> { + Basic { + accent: self.__unsafe_private_named.0.unwrap(), + accent_foreground: self.__unsafe_private_named.1.unwrap(), + background: self.__unsafe_private_named.2.unwrap(), + foreground: self.__unsafe_private_named.3.unwrap(), + extra_data: Some(extra_data), + } + } +} + +fn lexicon_doc_site_standard_theme_basic() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + ::jacquard_lexicon::lexicon::LexiconDoc { + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, + id: ::jacquard_common::CowStr::new_static("site.standard.theme.basic"), + revision: None, + description: None, + defs: { + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("main"), + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { + description: Some( + ::jacquard_common::CowStr::new_static( + "A simplified theme definition for publications, providing basic color customization for content display across different platforms and applications.", + ), + ), + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("background"), + ::jacquard_common::smol_str::SmolStr::new_static("foreground"), + ::jacquard_common::smol_str::SmolStr::new_static("accent"), + ::jacquard_common::smol_str::SmolStr::new_static("accentForeground") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("accent"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { + description: Some( + ::jacquard_common::CowStr::new_static( + "Color used for links and button backgrounds.", + ), + ), + refs: vec![ + ::jacquard_common::CowStr::new_static("site.standard.theme.color#rgb") + ], + closed: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "accentForeground", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { + description: Some( + ::jacquard_common::CowStr::new_static( + "Color used for button text.", + ), + ), + refs: vec![ + ::jacquard_common::CowStr::new_static("site.standard.theme.color#rgb") + ], + closed: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "background", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { + description: Some( + ::jacquard_common::CowStr::new_static( + "Color used for content background.", + ), + ), + refs: vec![ + ::jacquard_common::CowStr::new_static("site.standard.theme.color#rgb") + ], + closed: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "foreground", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { + description: Some( + ::jacquard_common::CowStr::new_static( + "Color used for content text.", + ), + ), + refs: vec![ + ::jacquard_common::CowStr::new_static("site.standard.theme.color#rgb") + ], + closed: None, + }), + ); + map + }, + }), + ); + map + }, + } +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Basic<'a> { + fn nsid() -> &'static str { + "site.standard.theme.basic" + } + fn def_name() -> &'static str { + "main" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_site_standard_theme_basic() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} diff --git a/lexicons-example/src/site_standard/theme/color.rs b/lexicons-example/src/site_standard/theme/color.rs new file mode 100644 index 0000000..4a8b068 --- /dev/null +++ b/lexicons-example/src/site_standard/theme/color.rs @@ -0,0 +1,718 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: site.standard.theme.color +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct Rgb<'a> { + pub b: i64, + pub g: i64, + pub r: i64, +} + +pub mod rgb_state { + + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type B; + type R; + type G; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type B = Unset; + type R = Unset; + type G = Unset; + } + ///State transition - sets the `b` field to Set + pub struct SetB(PhantomData S>); + impl sealed::Sealed for SetB {} + impl State for SetB { + type B = Set; + type R = S::R; + type G = S::G; + } + ///State transition - sets the `r` field to Set + pub struct SetR(PhantomData S>); + impl sealed::Sealed for SetR {} + impl State for SetR { + type B = S::B; + type R = Set; + type G = S::G; + } + ///State transition - sets the `g` field to Set + pub struct SetG(PhantomData S>); + impl sealed::Sealed for SetG {} + impl State for SetG { + type B = S::B; + type R = S::R; + type G = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `b` field + pub struct b(()); + ///Marker type for the `r` field + pub struct r(()); + ///Marker type for the `g` field + pub struct g(()); + } +} + +/// Builder for constructing an instance of this type +pub struct RgbBuilder<'a, S: rgb_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option, + ::core::option::Option, + ::core::option::Option, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> Rgb<'a> { + /// Create a new builder for this type + pub fn new() -> RgbBuilder<'a, rgb_state::Empty> { + RgbBuilder::new() + } +} + +impl<'a> RgbBuilder<'a, rgb_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + RgbBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None, None, None), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbBuilder<'a, S> +where + S: rgb_state::State, + S::B: rgb_state::IsUnset, +{ + /// Set the `b` field (required) + pub fn b(mut self, value: impl Into) -> RgbBuilder<'a, rgb_state::SetB> { + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); + RgbBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbBuilder<'a, S> +where + S: rgb_state::State, + S::G: rgb_state::IsUnset, +{ + /// Set the `g` field (required) + pub fn g(mut self, value: impl Into) -> RgbBuilder<'a, rgb_state::SetG> { + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); + RgbBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbBuilder<'a, S> +where + S: rgb_state::State, + S::R: rgb_state::IsUnset, +{ + /// Set the `r` field (required) + pub fn r(mut self, value: impl Into) -> RgbBuilder<'a, rgb_state::SetR> { + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); + RgbBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbBuilder<'a, S> +where + S: rgb_state::State, + S::B: rgb_state::IsSet, + S::R: rgb_state::IsSet, + S::G: rgb_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> Rgb<'a> { + Rgb { + b: self.__unsafe_private_named.0.unwrap(), + g: self.__unsafe_private_named.1.unwrap(), + r: self.__unsafe_private_named.2.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> Rgb<'a> { + Rgb { + b: self.__unsafe_private_named.0.unwrap(), + g: self.__unsafe_private_named.1.unwrap(), + r: self.__unsafe_private_named.2.unwrap(), + extra_data: Some(extra_data), + } + } +} + +fn lexicon_doc_site_standard_theme_color() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + ::jacquard_lexicon::lexicon::LexiconDoc { + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, + id: ::jacquard_common::CowStr::new_static("site.standard.theme.color"), + revision: None, + description: None, + defs: { + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("rgb"), + ::jacquard_lexicon::lexicon::LexUserType::Object( + ::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some(vec![ + ::jacquard_common::smol_str::SmolStr::new_static("r"), + ::jacquard_common::smol_str::SmolStr::new_static("g"), + ::jacquard_common::smol_str::SmolStr::new_static("b"), + ]), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("b"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer( + ::jacquard_lexicon::lexicon::LexInteger { + description: None, + default: None, + minimum: Some(0i64), + maximum: Some(255i64), + r#enum: None, + r#const: None, + }, + ), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("g"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer( + ::jacquard_lexicon::lexicon::LexInteger { + description: None, + default: None, + minimum: Some(0i64), + maximum: Some(255i64), + r#enum: None, + r#const: None, + }, + ), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("r"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer( + ::jacquard_lexicon::lexicon::LexInteger { + description: None, + default: None, + minimum: Some(0i64), + maximum: Some(255i64), + r#enum: None, + r#const: None, + }, + ), + ); + map + }, + }, + ), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("rgba"), + ::jacquard_lexicon::lexicon::LexUserType::Object( + ::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some(vec![ + ::jacquard_common::smol_str::SmolStr::new_static("r"), + ::jacquard_common::smol_str::SmolStr::new_static("g"), + ::jacquard_common::smol_str::SmolStr::new_static("b"), + ::jacquard_common::smol_str::SmolStr::new_static("a"), + ]), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("a"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer( + ::jacquard_lexicon::lexicon::LexInteger { + description: None, + default: None, + minimum: Some(0i64), + maximum: Some(100i64), + r#enum: None, + r#const: None, + }, + ), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("b"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer( + ::jacquard_lexicon::lexicon::LexInteger { + description: None, + default: None, + minimum: Some(0i64), + maximum: Some(255i64), + r#enum: None, + r#const: None, + }, + ), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("g"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer( + ::jacquard_lexicon::lexicon::LexInteger { + description: None, + default: None, + minimum: Some(0i64), + maximum: Some(255i64), + r#enum: None, + r#const: None, + }, + ), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("r"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer( + ::jacquard_lexicon::lexicon::LexInteger { + description: None, + default: None, + minimum: Some(0i64), + maximum: Some(255i64), + r#enum: None, + r#const: None, + }, + ), + ); + map + }, + }, + ), + ); + map + }, + } +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Rgb<'a> { + fn nsid() -> &'static str { + "site.standard.theme.color" + } + fn def_name() -> &'static str { + "rgb" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_site_standard_theme_color() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + { + let value = &self.b; + if *value > 255i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("b"), + max: 255i64, + actual: *value, + }); + } + } + { + let value = &self.b; + if *value < 0i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("b"), + min: 0i64, + actual: *value, + }); + } + } + { + let value = &self.g; + if *value > 255i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("g"), + max: 255i64, + actual: *value, + }); + } + } + { + let value = &self.g; + if *value < 0i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("g"), + min: 0i64, + actual: *value, + }); + } + } + { + let value = &self.r; + if *value > 255i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("r"), + max: 255i64, + actual: *value, + }); + } + } + { + let value = &self.r; + if *value < 0i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("r"), + min: 0i64, + actual: *value, + }); + } + } + Ok(()) + } +} + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, +)] +#[serde(rename_all = "camelCase")] +pub struct Rgba<'a> { + pub a: i64, + pub b: i64, + pub g: i64, + pub r: i64, +} + +pub mod rgba_state { + + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type A; + type R; + type B; + type G; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type A = Unset; + type R = Unset; + type B = Unset; + type G = Unset; + } + ///State transition - sets the `a` field to Set + pub struct SetA(PhantomData S>); + impl sealed::Sealed for SetA {} + impl State for SetA { + type A = Set; + type R = S::R; + type B = S::B; + type G = S::G; + } + ///State transition - sets the `r` field to Set + pub struct SetR(PhantomData S>); + impl sealed::Sealed for SetR {} + impl State for SetR { + type A = S::A; + type R = Set; + type B = S::B; + type G = S::G; + } + ///State transition - sets the `b` field to Set + pub struct SetB(PhantomData S>); + impl sealed::Sealed for SetB {} + impl State for SetB { + type A = S::A; + type R = S::R; + type B = Set; + type G = S::G; + } + ///State transition - sets the `g` field to Set + pub struct SetG(PhantomData S>); + impl sealed::Sealed for SetG {} + impl State for SetG { + type A = S::A; + type R = S::R; + type B = S::B; + type G = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `a` field + pub struct a(()); + ///Marker type for the `r` field + pub struct r(()); + ///Marker type for the `b` field + pub struct b(()); + ///Marker type for the `g` field + pub struct g(()); + } +} + +/// Builder for constructing an instance of this type +pub struct RgbaBuilder<'a, S: rgba_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option, + ::core::option::Option, + ::core::option::Option, + ::core::option::Option, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> Rgba<'a> { + /// Create a new builder for this type + pub fn new() -> RgbaBuilder<'a, rgba_state::Empty> { + RgbaBuilder::new() + } +} + +impl<'a> RgbaBuilder<'a, rgba_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + RgbaBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None, None, None, None), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbaBuilder<'a, S> +where + S: rgba_state::State, + S::A: rgba_state::IsUnset, +{ + /// Set the `a` field (required) + pub fn a(mut self, value: impl Into) -> RgbaBuilder<'a, rgba_state::SetA> { + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); + RgbaBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbaBuilder<'a, S> +where + S: rgba_state::State, + S::B: rgba_state::IsUnset, +{ + /// Set the `b` field (required) + pub fn b(mut self, value: impl Into) -> RgbaBuilder<'a, rgba_state::SetB> { + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); + RgbaBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbaBuilder<'a, S> +where + S: rgba_state::State, + S::G: rgba_state::IsUnset, +{ + /// Set the `g` field (required) + pub fn g(mut self, value: impl Into) -> RgbaBuilder<'a, rgba_state::SetG> { + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); + RgbaBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbaBuilder<'a, S> +where + S: rgba_state::State, + S::R: rgba_state::IsUnset, +{ + /// Set the `r` field (required) + pub fn r(mut self, value: impl Into) -> RgbaBuilder<'a, rgba_state::SetR> { + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); + RgbaBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> RgbaBuilder<'a, S> +where + S: rgba_state::State, + S::A: rgba_state::IsSet, + S::R: rgba_state::IsSet, + S::B: rgba_state::IsSet, + S::G: rgba_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> Rgba<'a> { + Rgba { + a: self.__unsafe_private_named.0.unwrap(), + b: self.__unsafe_private_named.1.unwrap(), + g: self.__unsafe_private_named.2.unwrap(), + r: self.__unsafe_private_named.3.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> Rgba<'a> { + Rgba { + a: self.__unsafe_private_named.0.unwrap(), + b: self.__unsafe_private_named.1.unwrap(), + g: self.__unsafe_private_named.2.unwrap(), + r: self.__unsafe_private_named.3.unwrap(), + extra_data: Some(extra_data), + } + } +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Rgba<'a> { + fn nsid() -> &'static str { + "site.standard.theme.color" + } + fn def_name() -> &'static str { + "rgba" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_site_standard_theme_color() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + { + let value = &self.a; + if *value > 100i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("a"), + max: 100i64, + actual: *value, + }); + } + } + { + let value = &self.a; + if *value < 0i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("a"), + min: 0i64, + actual: *value, + }); + } + } + { + let value = &self.b; + if *value > 255i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("b"), + max: 255i64, + actual: *value, + }); + } + } + { + let value = &self.b; + if *value < 0i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("b"), + min: 0i64, + actual: *value, + }); + } + } + { + let value = &self.g; + if *value > 255i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("g"), + max: 255i64, + actual: *value, + }); + } + } + { + let value = &self.g; + if *value < 0i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("g"), + min: 0i64, + actual: *value, + }); + } + } + { + let value = &self.r; + if *value > 255i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("r"), + max: 255i64, + actual: *value, + }); + } + } + { + let value = &self.r; + if *value < 0i64 { + return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { + path: ::jacquard_lexicon::validation::ValidationPath::from_field("r"), + min: 0i64, + actual: *value, + }); + } + } + Ok(()) + } +} diff --git a/standard-site-sync/Cargo.toml b/standard-site-sync/Cargo.toml new file mode 100644 index 0000000..d48cd5a --- /dev/null +++ b/standard-site-sync/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "standard-site-sync" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Example sync for standard.site publications and documents" +authors = ["Thomas Karpiniec "] +publish = false + +[[bin]] +name = "standard-site-sync" +path = "src/main.rs" + +[dependencies] +tapped = { path = "../tapped" } +lexicons-example = { path = "../lexicons-example" } + +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +env_logger = "0.11" +log = "0.4" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +jacquard-common = "0.9.5" +jacquard-lexicon = "0.9.5" diff --git a/standard-site-sync/src/main.rs b/standard-site-sync/src/main.rs new file mode 100644 index 0000000..e5c7c9e --- /dev/null +++ b/standard-site-sync/src/main.rs @@ -0,0 +1,275 @@ +//! Example: Sync site.standard.publication and site.standard.document records. + +use std::collections::HashMap; +use std::fs; + +use jacquard_lexicon::schema::LexiconSchema; +use log::{error, info}; +use serde::{Deserialize, Serialize}; +use tapped::{Event, RecordAction, RecordEvent, TapConfig, TapProcess}; + +use lexicons_example::site_standard::document::Document; +use lexicons_example::site_standard::publication::Publication; + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct CachedPublication { + url: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct CachedDocument { + /// Either an at:// URI (for attached docs) or http(s) URL (for floating docs) + site_ref: String, + /// The path within the site + path: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +struct Cache { + /// Publications keyed by at:// URI + publications: HashMap, + /// Documents keyed by at:// URI + documents: HashMap, +} + +/// Construct an at:// URI from a record event. +fn make_at_uri(event: &RecordEvent) -> String { + format!("at://{}/{}/{}", event.did, event.collection, event.rkey) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_timestamp_secs() + .init(); + + info!("Starting standard.site sync example"); + + let config = TapConfig::builder() + .database_url("sqlite://./tap-example.db") + .bind(":2480") + .signal_collection("site.standard.document") + .collection_filters(vec![ + "site.standard.publication".to_string(), + "site.standard.document".to_string(), + ]) + .disable_acks(false) + .inherit_stdio(true) + .build(); + + info!("Spawning tap process..."); + + // Spawn tap (looks for ./tap first, then tap on PATH) + let process = TapProcess::spawn_default(config).await?; + info!("Tap running at {}", process.url()); + + let client = process.client()?; + client.health().await?; + info!("Tap is healthy!"); + + let mut receiver = client.channel().await?; + info!("Connected! Waiting for events..."); + + // In-memory cache - load from disk if available + let mut cache: Cache = load_cache_from_disk(); + info!( + "Loaded cache from disk: {} publications, {} documents", + cache.publications.len(), + cache.documents.len() + ); + + let mut live_count = 0u64; + let mut backfill_count = 0u64; + + loop { + match receiver.recv().await { + Ok(received) => { + if let Event::Record(ref record_event) = *received { + // Track live vs backfill + if record_event.live { + live_count += 1; + } else { + backfill_count += 1; + } + + process_record_event(record_event, &mut cache); + write_output_files(&cache)?; + + // Periodically show event source breakdown + if (live_count + backfill_count).is_multiple_of(10) { + info!( + "[Stats] Live events: {}, Backfill events: {}", + live_count, backfill_count + ); + } + } else if let Event::Identity(ref identity_event) = *received { + info!( + "[IDENTITY] {} -> {} (active: {})", + identity_event.did, identity_event.handle, identity_event.is_active + ); + } + // Event is automatically acked when `received` is dropped here + } + Err(e) => { + eprintln!("Error receiving event: {}", e); + break; + } + } + } + + Ok(()) +} + +fn process_record_event(event: &RecordEvent, cache: &mut Cache) { + let at_uri = make_at_uri(event); + let action_str = match event.action { + RecordAction::Create => "CREATE", + RecordAction::Update => "UPDATE", + RecordAction::Delete => "DELETE", + _ => "UNKNOWN", + }; + + // Show whether this is a live or backfill event + let source = if event.live { "LIVE" } else { "BACKFILL" }; + + info!( + "[{} {}] {} {}/{}", + action_str, source, event.did, event.collection, event.rkey + ); + + match event.action { + RecordAction::Create | RecordAction::Update => match event.collection.as_str() { + "site.standard.publication" => { + if let Some(json) = event.record_as_str() { + match serde_json::from_str::>(json) { + Ok(publication) => { + match publication.validate() { + Ok(_) => { + info!("publication validation ok"); + } + Err(e) => { + error!("publication couldn't validate {:?}", e); + return; + } + } + + info!( + "Publication: {} ({})", + publication.name.as_str(), + publication.url.as_str() + ); + cache.publications.insert( + at_uri, + CachedPublication { + url: publication.url.as_str().to_owned(), + }, + ); + } + Err(e) => { + error!("publication could not be deserialised: {:?}", e); + } + } + } + } + "site.standard.document" => { + if let Some(json) = event.record_as_str() { + match serde_json::from_str::>(json) { + Ok(document) => { + match document.validate() { + Ok(_) => { + info!("document validation ok"); + } + Err(e) => { + error!("document couldn't validate {:?}", e); + return; + } + } + info!("Document: {}", document.title); + cache.documents.insert( + at_uri, + CachedDocument { + site_ref: document.site.as_str().to_string(), + path: document.path.map(|p| p.as_str().to_string()), + }, + ); + } + Err(e) => { + error!("document could not be deserialised: {:?}", e); + } + } + } + } + _ => {} + }, + RecordAction::Delete => match event.collection.as_str() { + "site.standard.publication" => { + cache.publications.remove(&at_uri); + } + "site.standard.document" => { + cache.documents.remove(&at_uri); + } + _ => {} + }, + _ => {} + } + + // Log cache stats + info!( + "Cached: {} publications, {} documents", + cache.publications.len(), + cache.documents.len() + ); +} + +fn write_output_files(cache: &Cache) -> Result<(), std::io::Error> { + let mut publication_urls: Vec<&str> = cache + .publications + .values() + .map(|p| p.url.as_str()) + .collect(); + publication_urls.sort(); + fs::write("publications.txt", publication_urls.join("\n"))?; + + let mut document_urls: Vec = Vec::new(); + + for doc in cache.documents.values() { + let Some(path) = &doc.path else { continue }; + + let base_url = if doc.site_ref.starts_with("at://") { + match cache.publications.get(&doc.site_ref) { + Some(pub_record) => &pub_record.url, + None => continue, // Skip if we don't know about this publication + } + } else if doc.site_ref.starts_with("http://") || doc.site_ref.starts_with("https://") { + // Floating document with direct URL + &doc.site_ref + } else { + continue; + }; + + let full_url = format!("{}{}", base_url.trim_end_matches('/'), path); + document_urls.push(full_url); + } + + document_urls.sort(); + fs::write("documents.txt", document_urls.join("\n"))?; + + // Write cache.json for persistence + let cache_json = serde_json::to_string_pretty(cache).map_err(std::io::Error::other)?; + fs::write("cache.json", cache_json)?; + + Ok(()) +} + +fn load_cache_from_disk() -> Cache { + match fs::read_to_string("cache.json") { + Ok(content) => match serde_json::from_str::(&content) { + Ok(cache) => cache, + Err(e) => { + log::warn!("Failed to parse cache.json: {}", e); + Cache::default() + } + }, + Err(_) => Cache::default(), + } +} diff --git a/tapped/Cargo.toml b/tapped/Cargo.toml new file mode 100644 index 0000000..6f01483 --- /dev/null +++ b/tapped/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "tapped" +version = "0.2.0" +edition = "2021" +description = "Rust wrapper for the tap ATProto utility" +license = "MIT" +readme = "../README.md" +authors = ["Thomas Karpiniec "] +keywords = ["atproto", "tap"] +repository = "https://tangled.org/octet-stream.net/tapped" + +[dependencies] +tokio = { version = "1", features = ["net", "process", "rt", "sync", "time"] } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } +tokio-tungstenite = { version = "0.28", features = ["rustls-tls-native-roots"] } +tungstenite = "0.28" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["raw_value"] } +thiserror = "2" +url = { version = "2", features = ["serde"] } +futures-util = "0.3" +tracing = "0.1" +base64 = "0.22" +libc = "0.2" +self_cell = "1.2.2" + +[dev-dependencies] +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/src/channel.rs b/tapped/src/channel.rs similarity index 64% rename from src/channel.rs rename to tapped/src/channel.rs index a350f65..a5a5a76 100644 --- a/src/channel.rs +++ b/tapped/src/channel.rs @@ -4,9 +4,10 @@ use futures_util::{SinkExt, StreamExt}; use serde::Serialize; use tokio::sync::mpsc; use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tungstenite::protocol::frame::Utf8Bytes; use url::Url; -use crate::types::RawEvent; +use crate::types::{RawEvent, RawEventOwned, RawRecordEventOwned, Record, UnparsedRecord}; use crate::{Error, Event, Result}; type WsStream = @@ -23,12 +24,12 @@ type WsSource = futures_util::stream::SplitStream; /// closes, `recv()` will return an error and you must create a new /// `EventReceiver` via [`TapClient::channel()`](crate::TapClient::channel). pub struct EventReceiver { - event_rx: mpsc::Receiver>, + event_rx: mpsc::Receiver, _ack_tx: mpsc::Sender, } struct EventWithAck { - event: Event, + event: Utf8Bytes, ack_tx: mpsc::Sender, } @@ -63,12 +64,6 @@ impl std::ops::Deref for ReceivedEvent { } } -impl std::fmt::Debug for ReceivedEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.event.fmt(f) - } -} - impl EventReceiver { /// Connect to a tap WebSocket channel. pub(crate) async fn connect(base_url: &Url, admin_password: Option<&str>) -> Result { @@ -99,8 +94,8 @@ impl EventReceiver { let (write, read) = ws_stream.split(); - let (event_tx, event_rx) = mpsc::channel::>(100); - let (ack_tx, ack_rx) = mpsc::channel::(1000); + let (event_tx, event_rx) = mpsc::channel(100); + let (ack_tx, ack_rx) = mpsc::channel(1000); let ack_tx_clone = ack_tx.clone(); tokio::spawn(async move { @@ -126,19 +121,50 @@ impl EventReceiver { /// /// Returns [`Error::ChannelClosed`] if the WebSocket connection closes. pub async fn recv(&mut self) -> Result { - match self.event_rx.recv().await { - Some(Ok(event_with_ack)) => { - let id = event_with_ack.event.id(); - Ok(ReceivedEvent { - event: event_with_ack.event, - _ack_guard: AckGuard { - id, - ack_tx: Some(event_with_ack.ack_tx), - }, - }) + loop { + match self.event_rx.recv().await { + Some(event_with_ack) => { + let mut raw_event_owned = None::; + let mut raw_record_owned = None::; + let inner_record = Record::new(event_with_ack.event, |json| { + match serde_json::from_str::(json) { + Ok(mut raw) => { + let inner = if let Some(mut rec) = raw.record.take() { + let inner = rec.record.take(); + raw_record_owned = Some(rec.owned); + inner + } else { + None + }; + raw_event_owned = Some(raw.owned); + UnparsedRecord(inner) + } + Err(e) => { + tracing::warn!("Failed to parse event: {}", e); + UnparsedRecord(None) + } + } + }); + let inner_record = if inner_record.borrow_dependent().0.is_some() { + Some(inner_record) + } else { + None + }; + if let Some(event) = + raw_event_owned.and_then(|r| r.into_event(raw_record_owned, inner_record)) + { + let id = event.id(); + break Ok(ReceivedEvent { + event, + _ack_guard: AckGuard { + id, + ack_tx: Some(event_with_ack.ack_tx), + }, + }); + } + } + None => break Err(Error::ChannelClosed), } - Some(Err(e)) => Err(e), - None => Err(Error::ChannelClosed), } } @@ -171,36 +197,27 @@ impl EventReceiver { /// Reader task: reads events from WebSocket and sends to channel. async fn reader_task( mut read: WsSource, - event_tx: mpsc::Sender>, + event_tx: mpsc::Sender, ack_tx: mpsc::Sender, ) { while let Some(msg_result) = read.next().await { match msg_result { - Ok(Message::Text(text)) => match serde_json::from_str::(&text) { - Ok(raw) => { - if let Some(event) = raw.into_event() { - let event_with_ack = EventWithAck { - event, - ack_tx: ack_tx.clone(), - }; - if event_tx.send(Ok(event_with_ack)).await.is_err() { - break; - } - } + Ok(Message::Text(event)) => { + let event_with_ack = EventWithAck { + event, + ack_tx: ack_tx.clone(), + }; + if event_tx.send(event_with_ack).await.is_err() { + break; } - Err(e) => { - tracing::warn!("Failed to parse event: {}", e); - } - }, + } Ok(Message::Close(_)) => { - let _ = event_tx.send(Err(Error::ChannelClosed)).await; break; } Ok(_) => { // Ignore ping/pong/binary } - Err(e) => { - let _ = event_tx.send(Err(Error::WebSocket(Box::new(e)))).await; + Err(_) => { break; } } diff --git a/src/client.rs b/tapped/src/client.rs similarity index 99% rename from src/client.rs rename to tapped/src/client.rs index 7d29cb8..f05edf7 100644 --- a/src/client.rs +++ b/tapped/src/client.rs @@ -350,7 +350,6 @@ impl TapClient { /// let mut receiver = client.channel().await?; /// /// while let Ok(event) = receiver.recv().await { - /// println!("Event: {:?}", event); /// // Event is automatically acknowledged when dropped /// } /// # Ok(()) diff --git a/src/config.rs b/tapped/src/config.rs similarity index 99% rename from src/config.rs rename to tapped/src/config.rs index c8f6b21..1d05f7b 100644 --- a/src/config.rs +++ b/tapped/src/config.rs @@ -42,7 +42,7 @@ pub struct TapConfig { pub max_db_conns: Option, // Server - /// HTTP server bind address (e.g., ":2480", "127.0.0.1:2480", or "[::1]:2480") + /// HTTP server bind address (e.g., `":2480"`, `"127.0.0.1:2480"`, or `"[::1]:2480"`) pub bind: Option, /// Basic auth admin password for all requests pub admin_password: Option, diff --git a/src/error.rs b/tapped/src/error.rs similarity index 94% rename from src/error.rs rename to tapped/src/error.rs index 6f6e7f2..f4fdbb9 100644 --- a/src/error.rs +++ b/tapped/src/error.rs @@ -64,4 +64,8 @@ pub enum Error { /// URL parse error #[error("URL parse error: {0}")] UrlParse(#[from] url::ParseError), + + /// No record present in event + #[error("No record present in event")] + NoRecordPresent, } diff --git a/src/handle.rs b/tapped/src/handle.rs similarity index 92% rename from src/handle.rs rename to tapped/src/handle.rs index 0ab1056..47d7fb1 100644 --- a/src/handle.rs +++ b/tapped/src/handle.rs @@ -11,8 +11,9 @@ use crate::Result; /// A convenience type that owns both a tap process and its client. /// /// `TapHandle` manages the lifecycle of a tap subprocess and provides -/// access to a [`TapClient`] for interacting with it. When dropped, -/// the process is gracefully shut down. +/// access to a [`TapClient`] for interacting with it. The process receives +/// SIGTERM when this handle is dropped. For graceful shutdown with timeout +/// handling, call [`shutdown()`](TapHandle::shutdown) explicitly. /// /// # Example /// @@ -33,7 +34,7 @@ use crate::Result; /// /// let mut channel = handle.channel().await?; /// while let Ok(event) = channel.recv().await { -/// println!("Event: {:?}", event.event); +/// // Handle event /// } /// /// Ok(()) diff --git a/src/lib.rs b/tapped/src/lib.rs similarity index 92% rename from src/lib.rs rename to tapped/src/lib.rs index f3f9653..f2288b0 100644 --- a/src/lib.rs +++ b/tapped/src/lib.rs @@ -32,7 +32,6 @@ //! // Stream events //! let mut receiver = client.channel().await?; //! while let Ok(event) = receiver.recv().await { -//! println!("Received event: {:?}", event); //! // Event is automatically acknowledged when dropped //! } //! @@ -55,8 +54,8 @@ pub use error::Error; pub use handle::TapHandle; pub use process::TapProcess; pub use types::{ - AccountStatus, Cursors, DidDocument, Event, IdentityEvent, Record, RecordAction, RecordEvent, - RepoInfo, RepoState, Service, VerificationMethod, + AccountStatus, Cursors, DidDocument, Event, IdentityEvent, RecordAction, RecordEvent, RepoInfo, + RepoState, Service, VerificationMethod, }; /// A specialised Result type for tapped operations. diff --git a/src/process.rs b/tapped/src/process.rs similarity index 97% rename from src/process.rs rename to tapped/src/process.rs index 790fd94..f76da28 100644 --- a/src/process.rs +++ b/tapped/src/process.rs @@ -12,7 +12,8 @@ use crate::{Error, Result, TapClient, TapConfig}; /// A running tap process. /// -/// The process is gracefully shut down when this struct is dropped. +/// The process receives SIGTERM when this struct is dropped. For graceful +/// shutdown with timeout handling, call [`shutdown()`](TapProcess::shutdown) explicitly. pub struct TapProcess { child: Child, url: Url, diff --git a/src/types.rs b/tapped/src/types.rs similarity index 82% rename from src/types.rs rename to tapped/src/types.rs index fd1047b..5c4a500 100644 --- a/src/types.rs +++ b/tapped/src/types.rs @@ -1,40 +1,22 @@ //! Type definitions for tap events and API responses. use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; -/// A record's JSON data with helper methods. -/// -/// Wraps the raw JSON value and provides convenient accessors. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Record(serde_json::Value); +use tungstenite::protocol::frame::Utf8Bytes; -impl Record { - /// Create a new Record from a JSON value. - pub fn new(value: serde_json::Value) -> Self { - Self(value) - } +use crate::Error; - /// Access the raw JSON value. - pub fn json(&self) -> &serde_json::Value { - &self.0 - } +self_cell::self_cell!( + pub(crate) struct Record { + owner: Utf8Bytes, - /// Consume and return the inner JSON value. - pub fn into_json(self) -> serde_json::Value { - self.0 + #[covariant] + dependent: UnparsedRecord, } +); - /// Returns the `$type` field (e.g., "app.bsky.feed.post"). - pub fn record_type(&self) -> Option<&str> { - self.0.get("$type")?.as_str() - } - - /// Deserialize the record into a user-provided type. - pub fn deserialize_as(&self) -> crate::Result { - Ok(serde_json::from_value(self.0.clone())?) - } -} +pub(crate) struct UnparsedRecord<'a>(pub(crate) Option<&'a RawValue>); /// Action performed on a record. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -74,7 +56,6 @@ pub enum RepoState { } /// A record event from the tap stream. -#[derive(Debug, Clone)] pub struct RecordEvent { /// Unique event ID for acknowledgment. pub id: u64, @@ -93,7 +74,28 @@ pub struct RecordEvent { /// CID of the record (None on delete). pub cid: Option, /// The record data (None on delete). - pub record: Option, + pub(crate) record: Option, +} + +impl RecordEvent { + /// Get the record's content as a reference to a JSON string + pub fn record_as_str(&self) -> Option<&str> { + self.record + .as_ref() + .and_then(|r| r.borrow_dependent().0) + .map(|rv| rv.get()) + } + + /// Parse the record's content to a compatible struct + pub fn deserialize_as(&self) -> Result + where + for<'de> T: Deserialize<'de>, + { + self.record_as_str() + .map_or(Err(Error::NoRecordPresent), |s| { + serde_json::from_str(s).map_err(Into::into) + }) + } } /// An identity event from the tap stream. @@ -112,7 +114,6 @@ pub struct IdentityEvent { } /// An event from the tap stream. -#[derive(Debug, Clone)] #[non_exhaustive] pub enum Event { /// A record create/update/delete event. @@ -216,17 +217,32 @@ pub struct Service { // Internal deserialisation structures for parsing tap's JSON format -#[derive(Deserialize)] -pub(crate) struct RawEvent { - pub id: u64, +#[derive(Deserialize, Clone)] +#[serde(bound(deserialize = "'de: 'a"))] +pub(crate) struct RawEvent<'a> { + #[serde(flatten)] + pub(crate) owned: RawEventOwned, + pub(crate) record: Option>, +} + +#[derive(Deserialize, Clone)] +pub(crate) struct RawEventOwned { + pub(crate) id: u64, #[serde(rename = "type")] - pub type_: String, - pub record: Option, - pub identity: Option, + pub(crate) type_: String, + pub(crate) identity: Option, } -#[derive(Deserialize)] -pub(crate) struct RawRecordEvent { +#[derive(Deserialize, Clone)] +#[serde(bound(deserialize = "'de: 'a"))] +pub(crate) struct RawRecordEvent<'a> { + #[serde(flatten)] + pub owned: RawRecordEventOwned, + pub record: Option<&'a RawValue>, +} + +#[derive(Deserialize, Clone)] +pub(crate) struct RawRecordEventOwned { pub live: bool, pub did: String, pub rev: String, @@ -234,10 +250,9 @@ pub(crate) struct RawRecordEvent { pub rkey: String, pub action: RecordAction, pub cid: Option, - pub record: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Clone)] pub(crate) struct RawIdentityEvent { pub did: String, pub handle: String, @@ -246,12 +261,16 @@ pub(crate) struct RawIdentityEvent { pub status: AccountStatus, } -impl RawEvent { +impl RawEventOwned { /// Convert to the public Event type. - pub fn into_event(self) -> Option { + pub fn into_event( + self, + raw_record: Option, + inner_record: Option, + ) -> Option { match self.type_.as_str() { "record" => { - let r = self.record?; + let r = raw_record?; Some(Event::Record(RecordEvent { id: self.id, live: r.live, @@ -261,7 +280,7 @@ impl RawEvent { rkey: r.rkey, action: r.action, cid: r.cid, - record: r.record.map(Record::new), + record: inner_record, })) } "identity" => { @@ -311,42 +330,6 @@ mod tests { use super::*; use serde_json::json; - #[test] - fn record_type_extraction() { - let record = Record::new(json!({ - "$type": "app.bsky.feed.post", - "text": "Hello, world!", - "createdAt": "2024-01-01T00:00:00Z" - })); - - assert_eq!(record.record_type(), Some("app.bsky.feed.post")); - } - - #[test] - fn record_type_missing() { - let record = Record::new(json!({ - "text": "No type field" - })); - - assert_eq!(record.record_type(), None); - } - - #[test] - fn record_deserialize_as() { - #[derive(Debug, Deserialize, PartialEq)] - struct SimpleRecord { - text: String, - } - - let record = Record::new(json!({ - "$type": "test.record", - "text": "Hello!" - })); - - let parsed: SimpleRecord = record.deserialize_as().unwrap(); - assert_eq!(parsed.text, "Hello!"); - } - /// Helper macro for testing enum variant deserialisation. macro_rules! assert_deserialize { ($type:ty, $($json:literal => $variant:expr),+ $(,)?) => { @@ -491,13 +474,17 @@ mod tests { "text": "Hello!" } } - }); + }) + .to_string(); - let raw: RawEvent = serde_json::from_value(json).unwrap(); - assert_eq!(raw.id, 12345); - assert_eq!(raw.type_, "record"); + let raw: RawEvent = serde_json::from_str(&json).unwrap(); + assert_eq!(raw.owned.id, 12345); + assert_eq!(raw.owned.type_, "record"); - let event = raw.into_event().unwrap(); + let event = raw + .owned + .into_event(raw.record.map(|r| r.owned), None) + .unwrap(); match event { Event::Record(r) => { assert_eq!(r.id, 12345); @@ -505,8 +492,6 @@ mod tests { assert_eq!(r.did, "did:plc:abc123"); assert_eq!(r.collection, "app.bsky.feed.post"); assert_eq!(r.action, RecordAction::Create); - assert!(r.record.is_some()); - assert_eq!(r.record.unwrap().record_type(), Some("app.bsky.feed.post")); } _ => panic!("Expected Record event"), } @@ -523,10 +508,11 @@ mod tests { "is_active": true, "status": "active" } - }); + }) + .to_string(); - let raw: RawEvent = serde_json::from_value(json).unwrap(); - let event = raw.into_event().unwrap(); + let raw: RawEvent = serde_json::from_str(&json).unwrap(); + let event = raw.owned.into_event(None, None).unwrap(); match event { Event::Identity(i) => { @@ -555,10 +541,14 @@ mod tests { "cid": null, "record": null } - }); - - let raw: RawEvent = serde_json::from_value(json).unwrap(); - let event = raw.into_event().unwrap(); + }) + .to_string(); + + let raw: RawEvent = serde_json::from_str(&json).unwrap(); + let event = raw + .owned + .into_event(raw.record.map(|r| r.owned), None) + .unwrap(); match event { Event::Record(r) => { diff --git a/tests/integration.rs b/tapped/tests/integration.rs similarity index 92% rename from tests/integration.rs rename to tapped/tests/integration.rs index 69d7e0e..dd32466 100644 --- a/tests/integration.rs +++ b/tapped/tests/integration.rs @@ -6,7 +6,6 @@ //! //! Run with: `cargo test --test integration -- --ignored` -use std::path::Path; use std::sync::atomic::{AtomicU16, Ordering}; use std::time::Duration; use tapped::{TapClient, TapConfig, TapHandle, TapProcess}; @@ -24,26 +23,9 @@ fn unique_test_config() -> (u16, String) { (id, db_url) } -/// Check if tap binary is available. -fn tap_available() -> bool { - Path::new("./tap").exists() || which::which("tap").is_ok() -} - -/// Skip test if tap is not available. -macro_rules! require_tap { - () => { - if !tap_available() { - eprintln!("Skipping test: tap binary not found"); - return; - } - }; -} - #[tokio::test] #[ignore = "requires tap binary"] async fn test_process_spawn_default() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) @@ -66,8 +48,6 @@ async fn test_process_spawn_default() { #[tokio::test] #[ignore = "requires tap binary"] async fn test_stats_endpoints() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) @@ -98,8 +78,6 @@ async fn test_stats_endpoints() { #[tokio::test] #[ignore = "requires tap binary"] async fn test_add_and_query_repos() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) @@ -137,8 +115,6 @@ async fn test_add_and_query_repos() { #[tokio::test] #[ignore = "requires tap binary"] async fn test_resolve_did() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) @@ -162,8 +138,6 @@ async fn test_resolve_did() { #[tokio::test] #[ignore = "requires tap binary"] async fn test_channel_connection() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) @@ -181,8 +155,6 @@ async fn test_channel_connection() { #[tokio::test] #[ignore = "requires tap binary"] async fn test_graceful_shutdown() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) @@ -205,8 +177,6 @@ async fn test_graceful_shutdown() { #[tokio::test] #[ignore = "requires tap binary"] async fn test_client_from_url() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) @@ -226,8 +196,6 @@ async fn test_client_from_url() { #[tokio::test] #[ignore = "requires tap binary"] async fn test_multiple_collection_filters() { - require_tap!(); - let (port, db_url) = unique_test_config(); let config = TapConfig::builder() .database_url(db_url) -- 2.51.2