A library for ATProtocol identities.
1# atproto-lexicon 2 3AT Protocol lexicon resolution and validation library for Rust. 4 5## Overview 6 7This library provides functionality for resolving and validating AT Protocol lexicons, which define the schema and structure of AT Protocol data. It implements the full lexicon resolution chain as specified by the AT Protocol: 8 91. Convert NSID to DNS name with `_lexicon` prefix 102. Perform DNS TXT lookup to get the authoritative DID 113. Resolve the DID to get the DID document 124. Extract PDS endpoint from the DID document 135. Make XRPC call to fetch the lexicon schema 14 15## Features 16 17- **Lexicon Resolution**: Resolve NSIDs to their schema definitions via DNS and XRPC 18- **Recursive Resolution**: Automatically resolve referenced lexicons with configurable depth limits 19- **NSID Validation**: Comprehensive validation of Namespace Identifiers 20- **Reference Extraction**: Extract and resolve lexicon references including fragment-only references 21- **Context-Aware Resolution**: Handle fragment-only references using lexicon ID as context 22- **CLI Tool**: Command-line interface for lexicon resolution 23 24## Installation 25 26Add this to your `Cargo.toml`: 27 28```toml 29[dependencies] 30atproto-lexicon = "0.13.0" 31``` 32 33## Usage 34 35### Basic Lexicon Resolution 36 37```rust 38use atproto_lexicon::resolve::{DefaultLexiconResolver, LexiconResolver}; 39use atproto_identity::resolve::HickoryDnsResolver; 40 41#[tokio::main] 42async fn main() -> anyhow::Result<()> { 43 let http_client = reqwest::Client::new(); 44 let dns_resolver = HickoryDnsResolver::create_resolver(&[]); 45 46 let resolver = DefaultLexiconResolver::new(http_client, dns_resolver); 47 48 // Resolve a single lexicon 49 let lexicon = resolver.resolve("app.bsky.feed.post").await?; 50 println!("Resolved lexicon: {}", serde_json::to_string_pretty(&lexicon)?); 51 52 Ok(()) 53} 54``` 55 56### Recursive Resolution 57 58```rust 59use atproto_lexicon::resolve_recursive::{RecursiveLexiconResolver, RecursiveResolverConfig}; 60 61#[tokio::main] 62async fn main() -> anyhow::Result<()> { 63 let http_client = reqwest::Client::new(); 64 let dns_resolver = HickoryDnsResolver::create_resolver(&[]); 65 let resolver = DefaultLexiconResolver::new(http_client, dns_resolver); 66 67 let config = RecursiveResolverConfig { 68 max_depth: 5, // Maximum recursion depth 69 include_entry: true, // Include the entry lexicon in results 70 }; 71 72 let recursive_resolver = RecursiveLexiconResolver::with_config(resolver, config); 73 74 // Resolve a lexicon and all its dependencies 75 let lexicons = recursive_resolver.resolve_recursive("app.bsky.feed.post").await?; 76 77 for (nsid, schema) in lexicons { 78 println!("Resolved {}: {} bytes", nsid, 79 serde_json::to_string(&schema)?.len()); 80 } 81 82 Ok(()) 83} 84``` 85 86### NSID Validation 87 88```rust 89use atproto_lexicon::validation::{ 90 is_valid_nsid, parse_nsid, nsid_to_dns_name, absolute 91}; 92 93// Validate NSIDs 94assert!(is_valid_nsid("app.bsky.feed.post")); 95assert!(!is_valid_nsid("invalid")); 96 97// Parse NSID components 98let parts = parse_nsid("app.bsky.feed.post#reply", None)?; 99assert_eq!(parts.parts, vec!["app", "bsky", "feed", "post"]); 100assert_eq!(parts.fragment, Some("reply".to_string())); 101 102// Convert parsed NSID back to string using Display trait 103println!("Parsed NSID: {}", parts); // Outputs: app.bsky.feed.post#reply 104 105// Convert NSID to DNS name for resolution 106let dns_name = nsid_to_dns_name("app.bsky.feed.post")?; 107assert_eq!(dns_name, "_lexicon.feed.bsky.app"); 108 109// Make fragment-only references absolute 110assert_eq!(absolute("app.bsky.feed.post", "#reply"), "app.bsky.feed.post#reply"); 111assert_eq!(absolute("app.bsky.feed.post", "com.example.other"), "com.example.other"); 112``` 113 114### Extract Lexicon References 115 116```rust 117use atproto_lexicon::resolve_recursive::extract_lexicon_references; 118use serde_json::json; 119 120let schema = json!({ 121 "lexicon": 1, 122 "id": "app.bsky.feed.post", 123 "defs": { 124 "main": { 125 "type": "record", 126 "record": { 127 "type": "object", 128 "properties": { 129 "embed": { 130 "type": "union", 131 "refs": [ 132 { "type": "ref", "ref": "app.bsky.embed.images" }, 133 { "type": "ref", "ref": "#localref" } // Fragment reference 134 ] 135 } 136 } 137 } 138 } 139 } 140}); 141 142let references = extract_lexicon_references(&schema); 143// References will include: 144// - "app.bsky.embed.images" (external reference) 145// - "app.bsky.feed.post" (from #localref using the lexicon's id as context) 146``` 147 148## CLI Tool 149 150The crate includes a command-line tool for lexicon resolution: 151 152```bash 153# Build with CLI support 154cargo build --features clap --bin atproto-lexicon-resolve 155 156# Resolve a single lexicon 157cargo run --features clap --bin atproto-lexicon-resolve -- app.bsky.feed.post 158 159# Pretty print the output 160cargo run --features clap --bin atproto-lexicon-resolve -- --pretty app.bsky.feed.post 161 162# Recursively resolve all referenced lexicons 163cargo run --features clap --bin atproto-lexicon-resolve -- --recursive app.bsky.feed.post 164 165# Limit recursion depth 166cargo run --features clap --bin atproto-lexicon-resolve -- --recursive --max-depth 3 app.bsky.feed.post 167 168# Show dependency graph 169cargo run --features clap --bin atproto-lexicon-resolve -- --recursive --show-deps app.bsky.feed.post 170 171# List only direct references 172cargo run --features clap --bin atproto-lexicon-resolve -- --list-refs app.bsky.feed.post 173``` 174 175## Module Structure 176 177- **`errors`**: Structured error types for all lexicon operations 178- **`resolve`**: Core lexicon resolution implementation following AT Protocol specification 179- **`resolve_recursive`**: Recursive resolution with dependency tracking and cycle detection 180- **`validation`**: NSID validation, parsing, and helper functions 181 182## Key Types 183 184### `NsidParts` 185Represents a parsed NSID with its component parts and optional fragment: 186- `parts`: Vector of NSID components (e.g., `["app", "bsky", "feed", "post"]`) 187- `fragment`: Optional fragment identifier (e.g., `"reply"` for `#reply`) 188 189Implements `Display` trait for converting back to string format. 190 191### `RecursiveResolverConfig` 192Configuration for recursive resolution: 193- `max_depth`: Maximum recursion depth (default: 10) 194- `include_entry`: Whether to include the entry lexicon in results (default: true) 195 196### `RecursiveResolutionResult` 197Detailed results from recursive resolution: 198- `lexicons`: HashMap of resolved lexicons by NSID 199- `failed`: Set of NSIDs that couldn't be resolved 200- `dependencies`: Dependency graph showing which lexicons reference which 201 202## Features 203 204- **Fragment-Only Reference Resolution**: Automatically resolves fragment-only references (e.g., `#localref`) using the lexicon's `id` field as context 205- **Union Type Support**: Extracts references from both `ref` objects and `union` types with `refs` arrays 206- **DNS-based Discovery**: Implements the AT Protocol DNS-based lexicon discovery mechanism 207- **Cycle Detection**: Prevents infinite recursion when resolving circular dependencies 208- **Validation**: Comprehensive NSID validation following AT Protocol specifications 209 210## Error Handling 211 212The library uses structured error types following the project convention `error-atproto-lexicon-<domain>-<number>`: 213 214- **`LexiconResolveError`**: Resolution errors (no DIDs found, invalid DID format, PDS errors) 215- **`LexiconValidationError`**: NSID format and validation errors 216- **`LexiconSchemaError`**: Schema structure and parsing errors 217- **`LexiconRecursiveError`**: Errors specific to recursive resolution 218 219All errors implement the `Error` trait and provide detailed context about failures. 220 221## Dependencies 222 223- `atproto-identity`: For DID resolution and DNS operations 224- `atproto-client`: For XRPC communication 225- `serde_json`: For JSON schema handling 226- `async-trait`: For async trait definitions 227- `tracing`: For structured logging 228 229## License 230 231This project is part of the atproto-identity-rs workspace. See the root LICENSE file for details. 232 233## Contributing 234 235Contributions are welcome! Please feel free to submit a Pull Request.