A better Rust ATProto crate

dependency tree cleanup, fixing examples, nicer justfile

Orual c27a050b d478b2fb

-59
Cargo.lock
···
]
[[package]]
-
name = "async-stream"
-
version = "0.3.6"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
-
dependencies = [
-
"async-stream-impl",
-
"futures-core",
-
"pin-project-lite",
-
]
-
-
[[package]]
-
name = "async-stream-impl"
-
version = "0.3.6"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
-
dependencies = [
-
"proc-macro2",
-
"quote",
-
"syn 2.0.106",
-
]
-
-
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"thiserror 2.0.17",
"tokio",
"url",
-
"urlencoding",
[[package]]
···
"serde_json",
"thiserror 2.0.17",
"tokio",
-
"tokio-test",
"tower",
"tower-http",
"tracing",
"tracing-subscriber",
-
"urlencoding",
[[package]]
···
"serde_html_form",
"serde_ipld_dagcbor",
"serde_json",
-
"serde_with",
"signature",
"smol_str",
"thiserror 2.0.17",
"tokio",
-
"trait-variant",
"url",
···
name = "jacquard-derive"
version = "0.5.1"
dependencies = [
-
"heck 0.5.0",
-
"itertools",
"jacquard-common 0.5.1",
-
"prettyplease",
"proc-macro2",
"quote",
"serde",
"serde_json",
-
"serde_repr",
-
"serde_with",
"syn 2.0.106",
···
"clap",
"glob",
"heck 0.5.0",
-
"itertools",
"jacquard-api 0.5.1 (git+https://tangled.org/@nonbinary.computer/jacquard)",
"jacquard-common 0.5.1 (git+https://tangled.org/@nonbinary.computer/jacquard)",
"jacquard-identity 0.5.1 (git+https://tangled.org/@nonbinary.computer/jacquard)",
···
"tokio",
"trait-variant",
"url",
-
"uuid",
"webbrowser",
···
[[package]]
-
name = "tokio-stream"
-
version = "0.1.17"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
-
dependencies = [
-
"futures-core",
-
"pin-project-lite",
-
"tokio",
-
]
-
-
[[package]]
-
name = "tokio-test"
-
version = "0.4.4"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
-
dependencies = [
-
"async-stream",
-
"bytes",
-
"futures-core",
-
"tokio",
-
"tokio-stream",
-
]
-
-
[[package]]
name = "tokio-util"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
-
"getrandom 0.3.3",
"js-sys",
"wasm-bindgen",
+1 -1
README.md
···
```
-
If you have `just` installed, you can run the [examples](https://tangled.org/@nonbinary.computer/jacquard/tree/main/examples) using `just example-{example-name} {ARGS}`
+
If you have `just` installed, you can run the [examples](https://tangled.org/@nonbinary.computer/jacquard/tree/main/examples) using `just example {example-name} {ARGS}` or `just examples` to see what's available.
## Component crates
+4 -5
crates/jacquard-axum/Cargo.toml
···
[[example]]
name = "axum_server"
path = "../../examples/axum_server.rs"
-
required-features = ["jacquard/fancy"]
[dependencies]
axum = "0.8.6"
-
axum-macros = "0.5.0"
bytes.workspace = true
jacquard = { version = "0.5", path = "../jacquard" }
jacquard-common = { version = "0.5", path = "../jacquard-common", features = ["reqwest-client"] }
···
tokio.workspace = true
tower-http = { version = "0.6.6", features = ["trace", "tracing"] }
tracing = "0.1.41"
-
tracing-subscriber = { version = "0.3.20", features = ["env-filter", "time"] }
-
urlencoding.workspace = true
[features]
default = ["service-auth"]
service-auth = ["jacquard-common/service-auth", "dep:jacquard-identity", "dep:multibase"]
[dev-dependencies]
+
axum-macros = "0.5.0"
axum-test = "18.1.0"
base64.workspace = true
chrono.workspace = true
k256 = { version = "0.13", features = ["ecdsa"] }
+
miette = { workspace = true, features = ["fancy"] }
rand = "0.8"
reqwest.workspace = true
serde_json.workspace = true
-
tokio-test = "0.4.4"
+
#tokio-test = "0.4.4"
tower = { version = "0.5", features = ["util"] }
+
tracing-subscriber = { version = "0.3.20", features = ["env-filter", "time"] }
-2
crates/jacquard-common/Cargo.toml
···
serde.workspace = true
serde_html_form.workspace = true
serde_json.workspace = true
-
serde_with.workspace = true
smol_str.workspace = true
thiserror.workspace = true
url.workspace = true
···
tokio = { workspace = true, features = ["sync"] }
reqwest = { workspace = true, optional = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
serde_ipld_dagcbor.workspace = true
-
trait-variant.workspace = true
signature = { version = "2", optional = true }
[features]
+2 -8
crates/jacquard-derive/Cargo.toml
···
proc-macro = true
[dependencies]
-
heck.workspace = true
-
itertools.workspace = true
-
prettyplease.workspace = true
proc-macro2.workspace = true
quote.workspace = true
-
serde.workspace = true
-
serde_json.workspace = true
-
serde_repr.workspace = true
-
serde_with.workspace = true
syn.workspace = true
-
[dev-dependencies]
jacquard-common = { version = "0.5", path = "../jacquard-common" }
+
serde.workspace = true
+
serde_json.workspace = true
+1 -1
crates/jacquard-lexicon/Cargo.toml
···
clap.workspace = true
glob = "0.3"
heck.workspace = true
-
itertools.workspace = true
+
#itertools.workspace = true
jacquard-api = { version = "0.5", git = "https://tangled.org/@nonbinary.computer/jacquard" }
jacquard-common = { version = "0.5", git = "https://tangled.org/@nonbinary.computer/jacquard" }
jacquard-identity = { version = "0.5", git = "https://tangled.org/@nonbinary.computer/jacquard" }
-1
crates/jacquard-oauth/Cargo.toml
···
thiserror = { workspace = true }
serde_html_form = { workspace = true }
miette = { workspace = true }
-
uuid = { version = "1", features = ["v4","std"] }
p256 = { workspace = true, features = ["ecdsa"] }
signature = "2"
rand_core.workspace = true
+5 -14
crates/jacquard/Cargo.toml
···
# All captured generated lexicon API bindings
api_all = ["api_full", "jacquard-api/ufos"]
dns = ["jacquard-identity/dns"]
-
# Pretty debug prints for examples
-
fancy = ["miette/fancy"]
# Propagate loopback to oauth (server + browser helper)
loopback = ["jacquard-oauth/loopback", "jacquard-oauth/browser-open"]
···
[[example]]
name = "oauth_timeline"
path = "../../examples/oauth_timeline.rs"
-
required-features = ["fancy"]
[[example]]
name = "create_post"
path = "../../examples/create_post.rs"
-
required-features = ["fancy"]
[[example]]
name = "post_with_image"
path = "../../examples/post_with_image.rs"
-
required-features = ["fancy"]
[[example]]
name = "update_profile"
path = "../../examples/update_profile.rs"
-
required-features = ["fancy"]
[[example]]
name = "public_atproto_feed"
···
[[example]]
name = "create_whitewind_post"
path = "../../examples/create_whitewind_post.rs"
-
required-features = ["fancy", ]
[[example]]
name = "read_whitewind_post"
path = "../../examples/read_whitewind_post.rs"
-
required-features = ["fancy"]
[[example]]
name = "read_tangled_repo"
path = "../../examples/read_tangled_repo.rs"
-
required-features = ["fancy"]
[[example]]
name = "resolve_did"
path = "../../examples/resolve_did.rs"
-
required-features = ["fancy"]
[[example]]
name = "update_preferences"
path = "../../examples/update_preferences.rs"
-
required-features = ["fancy"]
[dependencies]
jacquard-api = { version = "0.5", path = "../jacquard-api" }
···
bon.workspace = true
async-trait.workspace = true
bytes.workspace = true
-
clap.workspace = true
http.workspace = true
miette = { workspace = true }
reqwest = { workspace = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
···
url.workspace = true
smol_str.workspace = true
percent-encoding.workspace = true
-
urlencoding.workspace = true
jose-jwk = { workspace = true, features = ["p256"] }
p256 = { workspace = true, features = ["ecdsa"] }
rand_core.workspace = true
+
[dev-dependencies]
+
clap.workspace = true
+
miette = { workspace = true, features = ["fancy"] }
+
[package.metadata.docs.rs]
-
features = [ "api_all", "derive", "dns", "fancy", "loopback" ]
+
features = [ "api_all", "derive", "dns", "loopback" ]
+1 -6
examples/axum_server.rs
···
use std::sync::Arc;
-
use axum::{
-
Json, Router,
-
extract::State,
-
http::{StatusCode, header},
-
response::IntoResponse,
-
};
+
use axum::{Json, Router, extract::State, http::StatusCode, response::IntoResponse};
use jacquard::{
api::com_atproto::identity::resolve_did::{ResolveDidOutput, ResolveDidRequest},
identity::{JacquardResolver, resolver::IdentityResolver},
+2 -5
examples/create_post.rs
···
use clap::Parser;
+
use jacquard::CowStr;
use jacquard::api::app_bsky::feed::post::Post;
-
use jacquard::client::{Agent, FileAuthStore};
-
use jacquard::oauth::atproto::AtprotoClientMetadata;
+
use jacquard::client::{Agent, AgentSessionExt, FileAuthStore};
use jacquard::oauth::client::OAuthClient;
use jacquard::oauth::loopback::LoopbackConfig;
use jacquard::types::string::Datetime;
-
use jacquard::xrpc::XrpcClient;
-
use jacquard::CowStr;
-
use miette::IntoDiagnostic;
#[derive(Parser, Debug)]
#[command(author, version, about = "Create a simple post")]
+3 -5
examples/create_whitewind_post.rs
···
use clap::Parser;
use jacquard::CowStr;
use jacquard::api::com_whtwnd::blog::entry::Entry;
-
use jacquard::client::{Agent, FileAuthStore};
-
use jacquard::oauth::atproto::AtprotoClientMetadata;
+
use jacquard::client::{Agent, AgentSessionExt, FileAuthStore};
use jacquard::oauth::client::OAuthClient;
use jacquard::oauth::loopback::LoopbackConfig;
use jacquard::types::string::Datetime;
-
use jacquard::xrpc::XrpcClient;
use miette::IntoDiagnostic;
use url::Url;
···
extra_data: Default::default(),
};
-
let mut output = agent.create_record(entry, None).await?;
+
let output = agent.create_record(entry, None).await?;
println!("Created WhiteWind blog post: {}", output.uri);
-
let url = Url::parse(format!(
+
let url = Url::parse(&format!(
"https://whtwnd.nat.vg/{}/{}",
output.uri.authority(),
output.uri.rkey().map(|r| r.as_ref()).unwrap_or("")
+1 -3
examples/post_with_image.rs
···
use jacquard::CowStr;
use jacquard::api::app_bsky::embed::images::{Image, Images};
use jacquard::api::app_bsky::feed::post::{Post, PostEmbed};
-
use jacquard::client::{Agent, FileAuthStore};
-
use jacquard::oauth::atproto::AtprotoClientMetadata;
+
use jacquard::client::{Agent, AgentSessionExt, FileAuthStore};
use jacquard::oauth::client::OAuthClient;
use jacquard::oauth::loopback::LoopbackConfig;
use jacquard::types::blob::MimeType;
use jacquard::types::string::Datetime;
-
use jacquard::xrpc::XrpcClient;
use miette::IntoDiagnostic;
use std::path::PathBuf;
+1 -1
examples/read_tangled_repo.rs
···
use clap::Parser;
use jacquard::api::sh_tangled::repo::Repo;
-
use jacquard::client::BasicClient;
+
use jacquard::client::{AgentSessionExt, BasicClient};
use jacquard::types::string::AtUri;
#[derive(Parser, Debug)]
+5 -2
examples/read_whitewind_post.rs
···
use clap::Parser;
use jacquard::api::com_whtwnd::blog::entry::Entry;
-
use jacquard::client::BasicClient;
+
use jacquard::client::{AgentSessionExt, BasicClient};
use jacquard::types::string::AtUri;
#[derive(Parser, Debug)]
···
println!("📚 WhiteWind Blog Entry\n");
println!("URI: {}", output.uri);
-
println!("Title: {}", output.value.title.as_deref().unwrap_or("[Untitled]"));
+
println!(
+
"Title: {}",
+
output.value.title.as_deref().unwrap_or("[Untitled]")
+
);
if let Some(subtitle) = &output.value.subtitle {
println!("Subtitle: {}", subtitle);
}
+1 -4
examples/update_profile.rs
···
use clap::Parser;
use jacquard::CowStr;
use jacquard::api::app_bsky::actor::profile::Profile;
-
use jacquard::client::{Agent, FileAuthStore};
-
use jacquard::oauth::atproto::AtprotoClientMetadata;
+
use jacquard::client::{Agent, AgentSessionExt, FileAuthStore};
use jacquard::oauth::client::OAuthClient;
use jacquard::oauth::loopback::LoopbackConfig;
use jacquard::types::string::AtUri;
-
use jacquard::xrpc::XrpcClient;
-
use miette::IntoDiagnostic;
#[derive(Parser, Debug)]
#[command(author, version, about = "Update profile display name and description")]
+48 -37
justfile
···
watch *ARGS:
bacon --job run -- -- {{ ARGS }}
-
# Run the OAuth timeline example
-
example-oauth *ARGS:
-
cargo run -p jacquard --example oauth_timeline --features fancy -- {{ARGS}}
+
update-api:
+
cargo run -p jacquard-lexicon --bin lex-fetch -- -v
-
# Create a simple post
-
example-create-post *ARGS:
-
cargo run -p jacquard --example create_post --features fancy -- {{ARGS}}
+
generate-api:
+
cargo run -p jacquard-lexicon --bin jacquard-codegen -- -i crates/jacquard-api/lexicons -o crates/jacquard-api/src -r crate
-
# Create a post with an image
-
example-post-image *ARGS:
-
cargo run -p jacquard --example post_with_image --features fancy -- {{ARGS}}
+
lex-gen *ARGS:
+
cargo run -p jacquard-lexicon --bin lex-fetch -- {{ARGS}}
-
# Update profile display name and description
-
example-update-profile *ARGS:
-
cargo run -p jacquard --example update_profile --features fancy -- {{ARGS}}
+
lex-fetch *ARGS:
+
cargo run -p jacquard-lexicon --bin lex-fetch -- --no-codegen {{ARGS}}
-
# Fetch public AT Protocol feed (no auth)
-
example-public-feed:
-
cargo run -p jacquard --example public_atproto_feed
+
codegen *ARGS:
+
cargo run -p jacquard-lexicon --bin jacquard-codegen -- -r crate {{ARGS}}
-
# Create a WhiteWind blog post
-
example-whitewind-create *ARGS:
-
cargo run -p jacquard --example create_whitewind_post --features fancy -- {{ARGS}}
+
# List all available examples
+
examples:
+
#!/usr/bin/env bash
+
echo "jacquard examples:"
+
for file in "examples"/*.rs; do
+
name=$(basename "$file" .rs)
+
echo " - $name"
+
done
+
echo ""
+
echo "jacquard-axum examples:"
+
cargo metadata --format-version=1 --no-deps | \
+
jq -r '.packages[] | select(.name == "jacquard-axum") | .targets[] | select(.kind[] == "example") | .name' | \
+
sed 's/^/ - /'
+
echo ""
+
echo "Usage: just example <name> [ARGS...]"
-
# Read a WhiteWind blog post
-
example-whitewind-read *ARGS:
-
cargo run -p jacquard --example read_whitewind_posts --features fancy -- {{ARGS}}
-
-
# Read info about a Tangled git repository
-
example-tangled-repo *ARGS:
-
cargo run -p jacquard --example read_tangled_repo --features fancy -- {{ARGS}}
-
-
# Resolve a handle to its DID document
-
example-resolve-did *ARGS:
-
cargo run -p jacquard --example resolve_did -- {{ARGS}}
-
-
# Update Bluesky preferences
-
example-update-preferences *ARGS:
-
cargo run -p jacquard --example update_preferences --features fancy -- {{ARGS}}
-
-
# Run the Axum server example
-
example-axum:
-
cargo run -p jacquard-axum --example axum_server --features jacquard/fancy
+
# Run an example by name (auto-detects package)
+
example NAME *ARGS:
+
#!/usr/bin/env bash
+
if [ -f "examples/{{NAME}}.rs" ]; then
+
cargo run -p jacquard --example {{NAME}} -- {{ARGS}}
+
elif cargo metadata --format-version=1 --no-deps | \
+
jq -e '.packages[] | select(.name == "jacquard-axum") | .targets[] | select(.kind[] == "example" and .name == "{{NAME}}")' > /dev/null; then
+
cargo run -p jacquard-axum --example {{NAME}} -- {{ARGS}}
+
else
+
echo "Example '{{NAME}}' not found."
+
echo ""
+
echo "jacquard examples:"
+
for file in "examples"/*.rs; do
+
name=$(basename "$file" .rs)
+
echo " - $name"
+
done
+
echo ""
+
echo "jacquard-axum examples:"
+
cargo metadata --format-version=1 --no-deps | \
+
jq -r '.packages[] | select(.name == "jacquard-axum") | .targets[] | select(.kind[] == "example") | .name' | \
+
sed 's/^/ - /'
+
exit 1
+
fi