diff --git a/Cargo.lock b/Cargo.lock index 867c804..89c6b7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,6 +282,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + [[package]] name = "base-x" version = "0.2.11" @@ -2162,6 +2171,12 @@ dependencies = [ "unsigned-varint 0.7.2", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2523,6 +2538,14 @@ dependencies = [ "slab", ] +[[package]] +name = "lexgen" +version = "0.1.0" +dependencies = [ + "jacquard-lexicon", + "miette", +] + [[package]] name = "libc" version = "0.2.189" @@ -2780,9 +2803,17 @@ version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" dependencies = [ + "backtrace", + "backtrace-ext", "cfg-if", "miette-derive", - "unicode-width", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", ] [[package]] @@ -2854,10 +2885,9 @@ dependencies = [ "hubble-sync-axum", "hubble-sync-rocksdb", "jacquard-axum", - "jacquard-lexicon", "metrics", "metrics-exporter-prometheus", - "miette", + "moots-lexicons", "rlimit", "rocksdb", "serde", @@ -2883,10 +2913,9 @@ dependencies = [ "hubble-sync-axum", "hubble-sync-rocksdb", "jacquard-axum", - "jacquard-lexicon", "metrics", "metrics-exporter-prometheus", - "miette", + "moots-lexicons", "rlimit", "rocksdb", "serde", @@ -2901,6 +2930,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "moots-lexicons" +version = "0.1.0" +dependencies = [ + "jacquard-common", + "jacquard-derive", + "jacquard-lexicon", + "miette", + "serde", + "thiserror 2.0.19", +] + [[package]] name = "multibase" version = "0.9.3" @@ -4266,6 +4307,27 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "syn" version = "2.0.119" @@ -4359,6 +4421,26 @@ dependencies = [ "utf-8", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4762,6 +4844,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-segmentation" version = "1.13.3" @@ -4774,6 +4862,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" diff --git a/Cargo.toml b/Cargo.toml index 22b2d12..d854ed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["moots-1-simple", "moots-2-read-optimized"] +members = ["moots-1-simple", "moots-2-read-optimized", "lexgen", "moots-lexicons"] [workspace.package] version = "0.1.0" @@ -14,9 +14,12 @@ hubble-sync = "0.0.4" hubble-sync-axum = "0.0.4" hubble-sync-rocksdb = "0.0.4" jacquard-axum = "0.12" -jacquard-lexicon = "0.12" +jacquard-common = "0.12" +jacquard-derive = "0.12" +jacquard-lexicon = { version = "0.12", default-features = false } metrics = "0.24" -miette = "7.6" # for lexicons codegenned by jacquard-lexicon +miette = "7.6" +moots-lexicons = { path = "moots-lexicons" } rlimit = "0.10" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/lexgen/Cargo.toml b/lexgen/Cargo.toml new file mode 100644 index 0000000..d298cf4 --- /dev/null +++ b/lexgen/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "lexgen" +version.workspace = true +edition.workspace = true + +[dependencies] +jacquard-lexicon = { workspace = true, features = ["codegen"] } +miette = { workspace = true, features = ["fancy"] } diff --git a/lexgen/src/main.rs b/lexgen/src/main.rs new file mode 100644 index 0000000..a6c1abc --- /dev/null +++ b/lexgen/src/main.rs @@ -0,0 +1,28 @@ +//! generate `moots-lexicons/src/` from `lexicons/**/*.json` +//! +//! to regenerate: +//! cargo run -p lexgen +//! +//! everything under `moots-lexicons/src/` is overwritten, including `lib.rs`. +//! `moots-lexicons/Cargo.toml` is hand-maintained: if any *top-level* lexicon +//! namespaces are added or removed, its `[features]` must be updated to match. + +use jacquard_lexicon::codegen::CodeGenerator; +use jacquard_lexicon::corpus::LexiconCorpus; +use std::path::PathBuf; + +fn main() -> miette::Result<()> { + let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_root = manifest.parent().expect("lexgen lives in the workspace"); + let lexicons_dir = workspace_root.join("lexicons"); + let out_dir = workspace_root.join("moots-lexicons").join("src"); + + let corpus = LexiconCorpus::load_from_dir(&lexicons_dir)?; + println!("loaded {} lexicon documents from {}", corpus.iter().count(), lexicons_dir.display()); + + CodeGenerator::new(&corpus, "crate").write_to_disk(&out_dir)?; + + println!("generated code written to {}", out_dir.display()); + println!("if any top-level namespaces changed, update [features] in moots-lexicons/Cargo.toml"); + Ok(()) +} diff --git a/lexicons/com/bad-example/graph/getBskyMutuals.json b/lexicons/com/bad-example/graph/getBskyMutuals.json new file mode 100644 index 0000000..d43d79a --- /dev/null +++ b/lexicons/com/bad-example/graph/getBskyMutuals.json @@ -0,0 +1,52 @@ +{ + "lexicon": 1, + "id": "com.bad-example.graph.getBskyMutuals", + "defs": { + "main": { + "type": "query", + "description": "Get an account's mutuals: the accounts it follows which also follow it back. Returns a paginated list of DIDs.", + "parameters": { + "type": "params", + "required": ["did"], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The account to list mutuals for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["mutuals"], + "properties": { + "cursor": { "type": "string" }, + "mutuals": { + "type": "array", + "items": { "type": "string", "format": "did" } + } + } + } + }, + "errors": [ + { + "name": "AccountNotFound", + "description": "The requested account is unknown to this service." + }, + { + "name": "AccountGone", + "description": "The requested account is known to have been deleted." + } + ] + } + } +} diff --git a/moots-1-simple/Cargo.toml b/moots-1-simple/Cargo.toml index 55113c5..73532ba 100644 --- a/moots-1-simple/Cargo.toml +++ b/moots-1-simple/Cargo.toml @@ -11,10 +11,9 @@ hubble-sync.workspace = true hubble-sync-axum.workspace = true hubble-sync-rocksdb.workspace = true jacquard-axum.workspace = true -jacquard-lexicon.workspace = true metrics.workspace = true metrics-exporter-prometheus.workspace = true -miette.workspace = true +moots-lexicons.workspace = true rlimit.workspace = true rocksdb.workspace = true serde.workspace = true @@ -29,6 +28,3 @@ tracing-subscriber.workspace = true [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator.workspace = true - -[dev-dependencies] -jacquard-lexicon = { workspace = true, features = ["codegen"] } diff --git a/moots-1-simple/readme.md b/moots-1-simple/readme.md new file mode 100644 index 0000000..ee97151 --- /dev/null +++ b/moots-1-simple/readme.md @@ -0,0 +1,21 @@ +# mutuals index: tutorial part 1 + +TODO: link to tutorial + +we index each follow from every account, and compute mutuals via one scan + N point lookups at read time. + +key shape: + +``` +follow:\0 +``` + +the `my_did` prefix carves out a key range for each repo to work in without conflicts, protected from self-concurrent conflicts by hubble-sync's actor model + +to compute mutuals, we scan keys under the `follow:\0` prefix to discover `your_did`s, and then for each `your_did`, look up `follow:\0`. (and then filter by active) + +nb: this doesn't account for moderation: + +- blocks (maybe pt 3? or should be pt 2?) +- block lists (harder) +- account-hiding labels (aaaaa) diff --git a/moots-2-read-optimized/Cargo.toml b/moots-2-read-optimized/Cargo.toml index 3376034..df53939 100644 --- a/moots-2-read-optimized/Cargo.toml +++ b/moots-2-read-optimized/Cargo.toml @@ -11,10 +11,9 @@ hubble-sync.workspace = true hubble-sync-axum.workspace = true hubble-sync-rocksdb.workspace = true jacquard-axum.workspace = true -jacquard-lexicon.workspace = true metrics.workspace = true metrics-exporter-prometheus.workspace = true -miette.workspace = true +moots-lexicons.workspace = true rlimit.workspace = true rocksdb.workspace = true serde.workspace = true @@ -29,6 +28,3 @@ tracing-subscriber.workspace = true [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator.workspace = true - -[dev-dependencies] -jacquard-lexicon = { workspace = true, features = ["codegen"] } diff --git a/moots-2-read-optimized/readme.md b/moots-2-read-optimized/readme.md new file mode 100644 index 0000000..ff8a507 --- /dev/null +++ b/moots-2-read-optimized/readme.md @@ -0,0 +1,15 @@ +# mutuals index: tutorial part 2 + +part 2 is still todo!!! + +part two will add an *index* of pre-computed mutuals for each account, eliminating the N point-reads for a single 100%-mutuals-hit-rate scan. + +this gets tricky because conflicts become possible, since index entries for mutuals have to be able to be written by *either* party. + +there are two ways we could add conflict avoidance, both which require a change to hubble-sync: + +- database-level conflict checking (optimistic or pessimistic). fairly straight-forward to add to the database engine crates, but we need to add a `Retry` variant to the main consumer-app callback error type to make it good. (we *could* desync on conflict, which is a *very* heavy kind of retry, but bleh. no.) + +- app-level: use mutexes/semaphores to serialize writes at the application level, possibly with sharding. this *almost* works without changes to hubble sync, the lock has to be guarded until the commit returns, and you can't do that in the callback. we'd need to add a way to pass the owned guard back to hubble-sync, who could explicitly hold it until post-commit. useful enough to add, but this is a real step in diy-ing your own concurrency problems, meaningfully more than OCC from the storage engine itself. + +going to add both of these to hubble-sync probably and they're not huge lifts. diff --git a/moots-lexicons/Cargo.toml b/moots-lexicons/Cargo.toml new file mode 100644 index 0000000..9417af0 --- /dev/null +++ b/moots-lexicons/Cargo.toml @@ -0,0 +1,28 @@ +# src/ generated from /lexicons, this file manually maintained +# cargo run -p moots-lexgen + +[package] +name = "moots-lexicons" +version.workspace = true +edition.workspace = true + +[features] +default = ["com_bad_example"] +com_bad_example = [] + +[dependencies] +jacquard-common.workspace = true +jacquard-derive.workspace = true +jacquard-lexicon.workspace = true +miette.workspace = true +serde.workspace = true +thiserror.workspace = true + +# lax rules for codegen +[lints.rust] +non_snake_case = "allow" +unused_imports = "allow" + +[lints.clippy] +new_ret_no_self = "allow" +new_without_default = "allow" diff --git a/moots-lexicons/src/builder_types.rs b/moots-lexicons/src/builder_types.rs new file mode 100644 index 0000000..cd45c2e --- /dev/null +++ b/moots-lexicons/src/builder_types.rs @@ -0,0 +1,45 @@ +// @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) + +#[jacquard_common::deps::codegen::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) + +#[jacquard_common::deps::codegen::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 {} +} \ No newline at end of file diff --git a/moots-lexicons/src/com_bad_example.rs b/moots-lexicons/src/com_bad_example.rs new file mode 100644 index 0000000..9d81497 --- /dev/null +++ b/moots-lexicons/src/com_bad_example.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. + +//! Generated bindings for the `com.bad.example` Lexicon namespace/module. +pub mod graph; \ No newline at end of file diff --git a/moots-lexicons/src/com_bad_example/graph.rs b/moots-lexicons/src/com_bad_example/graph.rs new file mode 100644 index 0000000..82c9acf --- /dev/null +++ b/moots-lexicons/src/com_bad_example/graph.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. + +//! Generated bindings for the `com.bad.example.graph` Lexicon namespace/module. +pub mod get_bsky_mutuals; \ No newline at end of file diff --git a/moots-lexicons/src/com_bad_example/graph/get_bsky_mutuals.rs b/moots-lexicons/src/com_bad_example/graph/get_bsky_mutuals.rs new file mode 100644 index 0000000..bb08e1f --- /dev/null +++ b/moots-lexicons/src/com_bad_example/graph/get_bsky_mutuals.rs @@ -0,0 +1,302 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: com.bad-example.graph.getBskyMutuals +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] + +#[serde( + rename_all = "camelCase", + bound(deserialize = "S: serde::Deserialize<'de> + jacquard_common::BosStr") +)] +pub struct GetBskyMutuals { + #[serde(skip_serializing_if = "core::option::Option::is_none")] + pub cursor: core::option::Option, + pub did: jacquard_common::types::string::Did, + /// Defaults to `50`. Min: 1. Max: 100. + #[serde(default = "_default_limit")] + #[serde(skip_serializing_if = "core::option::Option::is_none")] + pub limit: core::option::Option, +} + + +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] + +#[serde( + rename_all = "camelCase", + bound(deserialize = "S: serde::Deserialize<'de> + jacquard_common::BosStr") +)] +pub struct GetBskyMutualsOutput< + S: jacquard_common::BosStr = jacquard_common::DefaultStr, +> { + #[serde(skip_serializing_if = "core::option::Option::is_none")] + pub cursor: core::option::Option, + pub mutuals: Vec>, + #[serde(flatten, default, skip_serializing_if = "core::option::Option::is_none")] + pub extra_data: core::option::Option< + alloc::collections::BTreeMap< + jacquard_common::deps::smol_str::SmolStr, + jacquard_common::types::value::Data, + >, + >, +} + + +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + thiserror::Error, + miette::Diagnostic +)] + +#[serde(tag = "error", content = "message")] +pub enum GetBskyMutualsError { + /// The requested account is unknown to this service. + #[serde(rename = "AccountNotFound")] + AccountNotFound(core::option::Option), + /// The requested account is known to have been deleted. + #[serde(rename = "AccountGone")] + AccountGone(core::option::Option), + /// Catch-all for unknown error codes. + #[serde(untagged)] + Other { + error: jacquard_common::deps::smol_str::SmolStr, + message: Option, + }, +} + +impl core::fmt::Display for GetBskyMutualsError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::AccountNotFound(msg) => { + write!(f, "AccountNotFound")?; + if let Some(msg) = msg { + write!(f, ": {}", msg)?; + } + Ok(()) + } + Self::AccountGone(msg) => { + write!(f, "AccountGone")?; + if let Some(msg) = msg { + write!(f, ": {}", msg)?; + } + Ok(()) + } + Self::Other { error, message } => { + write!(f, "{}", error)?; + if let Some(msg) = message { + write!(f, ": {}", msg)?; + } + Ok(()) + } + } + } +} + +/** Response marker for the `com.bad-example.graph.getBskyMutuals` query. + +Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output`, which is `GetBskyMutualsOutput` for this endpoint.*/ +pub struct GetBskyMutualsResponse; +impl jacquard_common::xrpc::XrpcResp for GetBskyMutualsResponse { + const NSID: &'static str = "com.bad-example.graph.getBskyMutuals"; + const ENCODING: &'static str = "application/json"; + type Output = GetBskyMutualsOutput; + type Err = GetBskyMutualsError; +} + +impl jacquard_common::xrpc::XrpcRequest +for GetBskyMutuals { + const NSID: &'static str = "com.bad-example.graph.getBskyMutuals"; + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; + type Response = GetBskyMutualsResponse; +} + +/** Endpoint marker for the `com.bad-example.graph.getBskyMutuals` query. + +Path: `/xrpc/com.bad-example.graph.getBskyMutuals`. The request payload type is `GetBskyMutuals`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ +pub struct GetBskyMutualsRequest; +impl jacquard_common::xrpc::XrpcEndpoint for GetBskyMutualsRequest { + const PATH: &'static str = "/xrpc/com.bad-example.graph.getBskyMutuals"; + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; + type Request = GetBskyMutuals; + type Response = GetBskyMutualsResponse; +} + +fn _default_limit() -> core::option::Option { + Some(50i64) +} + +pub mod get_bsky_mutuals_state { + + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + #[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 Did; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Did = Unset; + } + ///State transition - sets the `did` field to Set + pub struct SetDid(PhantomData St>); + impl sealed::Sealed for SetDid {} + impl State for SetDid { + type Did = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `did` field + pub struct did(()); + } +} + +/// Builder for constructing an instance of this type. +pub struct GetBskyMutualsBuilder< + St: get_bsky_mutuals_state::State, + S: jacquard_common::BosStr = jacquard_common::DefaultStr, +> { + _state: ::core::marker::PhantomData St>, + _fields: ( + core::option::Option, + core::option::Option>, + core::option::Option, + ), + _type: ::core::marker::PhantomData S>, +} + +impl GetBskyMutuals { + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed + pub fn new() -> GetBskyMutualsBuilder< + get_bsky_mutuals_state::Empty, + jacquard_common::DefaultStr, + > { + GetBskyMutualsBuilder::new() + } +} + +impl GetBskyMutuals { + /// Create a new builder for this type + pub fn builder() -> GetBskyMutualsBuilder { + GetBskyMutualsBuilder::builder() + } +} + +impl GetBskyMutualsBuilder { + /// Create a new builder with all fields unset, using the default string type, if needed + pub fn new() -> Self { + GetBskyMutualsBuilder { + _state: ::core::marker::PhantomData, + _fields: (None, None, None), + _type: ::core::marker::PhantomData, + } + } +} + +impl< + S: jacquard_common::BosStr, +> GetBskyMutualsBuilder { + /// Create a new builder with all fields unset + pub fn builder() -> Self { + GetBskyMutualsBuilder { + _state: ::core::marker::PhantomData, + _fields: (None, None, None), + _type: ::core::marker::PhantomData, + } + } +} + +impl< + St: get_bsky_mutuals_state::State, + S: jacquard_common::BosStr, +> GetBskyMutualsBuilder { + /// Set the `cursor` field (optional) + pub fn cursor(mut self, value: impl Into>) -> Self { + self._fields.0 = value.into(); + self + } + /// Set the `cursor` field to an Option value (optional) + pub fn maybe_cursor(mut self, value: Option) -> Self { + self._fields.0 = value; + self + } +} + +impl GetBskyMutualsBuilder +where + St: get_bsky_mutuals_state::State, + St::Did: get_bsky_mutuals_state::IsUnset, +{ + /// Set the `did` field (required) + pub fn did( + mut self, + value: impl Into>, + ) -> GetBskyMutualsBuilder, S> { + self._fields.1 = ::core::option::Option::Some(value.into()); + GetBskyMutualsBuilder { + _state: ::core::marker::PhantomData, + _fields: self._fields, + _type: ::core::marker::PhantomData, + } + } +} + +impl< + St: get_bsky_mutuals_state::State, + S: jacquard_common::BosStr, +> GetBskyMutualsBuilder { + /// Set the `limit` field (optional) + pub fn limit(mut self, value: impl Into>) -> Self { + self._fields.2 = value.into(); + self + } + /// Set the `limit` field to an Option value (optional) + pub fn maybe_limit(mut self, value: Option) -> Self { + self._fields.2 = value; + self + } +} + +impl GetBskyMutualsBuilder +where + St: get_bsky_mutuals_state::State, + St::Did: get_bsky_mutuals_state::IsSet, +{ + /// Build the final struct. + pub fn build(self) -> GetBskyMutuals { + GetBskyMutuals { + cursor: self._fields.0, + did: self._fields.1.unwrap(), + limit: self._fields.2, + } + } +} \ No newline at end of file diff --git a/moots-lexicons/src/com_bad_example/graph/get_mutuals.rs b/moots-lexicons/src/com_bad_example/graph/get_mutuals.rs new file mode 100644 index 0000000..120e65c --- /dev/null +++ b/moots-lexicons/src/com_bad_example/graph/get_mutuals.rs @@ -0,0 +1,291 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: com.bad-example.graph.getMutuals +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] + +#[serde( + rename_all = "camelCase", + bound(deserialize = "S: serde::Deserialize<'de> + jacquard_common::BosStr") +)] +pub struct GetMutuals { + #[serde(skip_serializing_if = "core::option::Option::is_none")] + pub cursor: core::option::Option, + pub did: jacquard_common::types::string::Did, + /// Defaults to `50`. Min: 1. Max: 100. + #[serde(default = "_default_limit")] + #[serde(skip_serializing_if = "core::option::Option::is_none")] + pub limit: core::option::Option, +} + + +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] + +#[serde( + rename_all = "camelCase", + bound(deserialize = "S: serde::Deserialize<'de> + jacquard_common::BosStr") +)] +pub struct GetMutualsOutput { + #[serde(skip_serializing_if = "core::option::Option::is_none")] + pub cursor: core::option::Option, + pub mutuals: Vec>, + #[serde(flatten, default, skip_serializing_if = "core::option::Option::is_none")] + pub extra_data: core::option::Option< + alloc::collections::BTreeMap< + jacquard_common::deps::smol_str::SmolStr, + jacquard_common::types::value::Data, + >, + >, +} + + +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + thiserror::Error, + miette::Diagnostic +)] + +#[serde(tag = "error", content = "message")] +pub enum GetMutualsError { + /// The requested account is unknown to this service. + #[serde(rename = "AccountNotFound")] + AccountNotFound(core::option::Option), + /// The requested account is known to have been deleted. + #[serde(rename = "AccountGone")] + AccountGone(core::option::Option), + /// Catch-all for unknown error codes. + #[serde(untagged)] + Other { + error: jacquard_common::deps::smol_str::SmolStr, + message: Option, + }, +} + +impl core::fmt::Display for GetMutualsError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::AccountNotFound(msg) => { + write!(f, "AccountNotFound")?; + if let Some(msg) = msg { + write!(f, ": {}", msg)?; + } + Ok(()) + } + Self::AccountGone(msg) => { + write!(f, "AccountGone")?; + if let Some(msg) = msg { + write!(f, ": {}", msg)?; + } + Ok(()) + } + Self::Other { error, message } => { + write!(f, "{}", error)?; + if let Some(msg) = message { + write!(f, ": {}", msg)?; + } + Ok(()) + } + } + } +} + +/** Response marker for the `com.bad-example.graph.getMutuals` query. + +Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output`, which is `GetMutualsOutput` for this endpoint.*/ +pub struct GetMutualsResponse; +impl jacquard_common::xrpc::XrpcResp for GetMutualsResponse { + const NSID: &'static str = "com.bad-example.graph.getMutuals"; + const ENCODING: &'static str = "application/json"; + type Output = GetMutualsOutput; + type Err = GetMutualsError; +} + +impl jacquard_common::xrpc::XrpcRequest for GetMutuals { + const NSID: &'static str = "com.bad-example.graph.getMutuals"; + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; + type Response = GetMutualsResponse; +} + +/** Endpoint marker for the `com.bad-example.graph.getMutuals` query. + +Path: `/xrpc/com.bad-example.graph.getMutuals`. The request payload type is `GetMutuals`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ +pub struct GetMutualsRequest; +impl jacquard_common::xrpc::XrpcEndpoint for GetMutualsRequest { + const PATH: &'static str = "/xrpc/com.bad-example.graph.getMutuals"; + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; + type Request = GetMutuals; + type Response = GetMutualsResponse; +} + +fn _default_limit() -> core::option::Option { + Some(50i64) +} + +pub mod get_mutuals_state { + + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + #[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 Did; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Did = Unset; + } + ///State transition - sets the `did` field to Set + pub struct SetDid(PhantomData St>); + impl sealed::Sealed for SetDid {} + impl State for SetDid { + type Did = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `did` field + pub struct did(()); + } +} + +/// Builder for constructing an instance of this type. +pub struct GetMutualsBuilder< + St: get_mutuals_state::State, + S: jacquard_common::BosStr = jacquard_common::DefaultStr, +> { + _state: ::core::marker::PhantomData St>, + _fields: ( + core::option::Option, + core::option::Option>, + core::option::Option, + ), + _type: ::core::marker::PhantomData S>, +} + +impl GetMutuals { + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed + pub fn new() -> GetMutualsBuilder< + get_mutuals_state::Empty, + jacquard_common::DefaultStr, + > { + GetMutualsBuilder::new() + } +} + +impl GetMutuals { + /// Create a new builder for this type + pub fn builder() -> GetMutualsBuilder { + GetMutualsBuilder::builder() + } +} + +impl GetMutualsBuilder { + /// Create a new builder with all fields unset, using the default string type, if needed + pub fn new() -> Self { + GetMutualsBuilder { + _state: ::core::marker::PhantomData, + _fields: (None, None, None), + _type: ::core::marker::PhantomData, + } + } +} + +impl GetMutualsBuilder { + /// Create a new builder with all fields unset + pub fn builder() -> Self { + GetMutualsBuilder { + _state: ::core::marker::PhantomData, + _fields: (None, None, None), + _type: ::core::marker::PhantomData, + } + } +} + +impl GetMutualsBuilder { + /// Set the `cursor` field (optional) + pub fn cursor(mut self, value: impl Into>) -> Self { + self._fields.0 = value.into(); + self + } + /// Set the `cursor` field to an Option value (optional) + pub fn maybe_cursor(mut self, value: Option) -> Self { + self._fields.0 = value; + self + } +} + +impl GetMutualsBuilder +where + St: get_mutuals_state::State, + St::Did: get_mutuals_state::IsUnset, +{ + /// Set the `did` field (required) + pub fn did( + mut self, + value: impl Into>, + ) -> GetMutualsBuilder, S> { + self._fields.1 = ::core::option::Option::Some(value.into()); + GetMutualsBuilder { + _state: ::core::marker::PhantomData, + _fields: self._fields, + _type: ::core::marker::PhantomData, + } + } +} + +impl GetMutualsBuilder { + /// Set the `limit` field (optional) + pub fn limit(mut self, value: impl Into>) -> Self { + self._fields.2 = value.into(); + self + } + /// Set the `limit` field to an Option value (optional) + pub fn maybe_limit(mut self, value: Option) -> Self { + self._fields.2 = value; + self + } +} + +impl GetMutualsBuilder +where + St: get_mutuals_state::State, + St::Did: get_mutuals_state::IsSet, +{ + /// Build the final struct. + pub fn build(self) -> GetMutuals { + GetMutuals { + cursor: self._fields.0, + did: self._fields.1.unwrap(), + limit: self._fields.2, + } + } +} \ No newline at end of file diff --git a/moots-lexicons/src/lib.rs b/moots-lexicons/src/lib.rs new file mode 100644 index 0000000..4dc7404 --- /dev/null +++ b/moots-lexicons/src/lib.rs @@ -0,0 +1,48 @@ +// @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. + +//! Generated AT Protocol Lexicon bindings. +//! +//! This crate contains Rust bindings generated from Lexicon schemas. Most +//! application code uses these types through the `jacquard::api` re-export, +//! alongside `jacquard::Client` or one of the higher-level session helpers. +//! +//! ## Finding things +//! +//! Lexicon NSIDs map to Rust modules by replacing dots with module separators +//! and using snake_case for Rust identifiers. For example, +//! `app.bsky.feed.getTimeline` is generated under +//! `app_bsky::feed::get_timeline`. +//! +//! Top-level namespace modules are feature-gated. Enable the feature matching +//! the top-level namespace module, such as `app_bsky` or `com_atproto`, to use +//! those bindings. +//! +//! ## String backing +//! +//! Many generated data types are generic over a string backing type, +//! usually written as `S: BosStr = DefaultStr`. The generic is part of the +//! public API: use the default owned backing when you do not need to choose, +//! or select another backing such as `String`, `&str`, or `CowStr<'_>` when +//! that is useful for your code. +//! +//! Common examples omit the generic because the default is inferred in the +//! usual request/response paths. +//! +//! ## XRPC endpoints +//! +//! Endpoint modules contain request parameter/input structs, output structs, +//! error enums, and marker types implementing +//! `jacquard_common::xrpc::{XrpcRequest, XrpcResp, XrpcEndpoint}`. High-level +//! client code normally constructs the request struct or marker type and sends +//! it with `jacquard::Client`, then uses response helpers such as +//! `into_output()` for default-backed output. +//! +extern crate alloc; +pub mod builder_types; + + +#[cfg(feature = "com_bad_example")] +pub mod com_bad_example; \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1c1dd5b --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# mutuals index: hubble-sync tutorial + +wip