Aether Lexicon
ATProto Lexicon for Elixir.
Note: this is a work in progress. Currently only doing validation but will likely have more features in the future.
Validation Features:
- Complete ATProto validation - All primitive types, complex types, IPLD types, and XRPC endpoints
- String format validation - datetime, URI, DID, handle, NSID, CID, language tags, and more
- XRPC endpoint support - Validate query parameters, request/response bodies, subscriptions, and errors
- Cross-schema references - Full support for refs and unions across schemas
- Comprehensive constraint validation - min/max, length, enum, const, required, nullable
Installation
Add aether_lexicon to your list of dependencies in mix.exs:
def deps do
[
{:aether_lexicon, "~> 0.1.1"}
]
endQuick Start
Basic Validation
alias Aether.ATProto.Lexicon.Validation
# Define a schema
schema = %{
"lexicon" => 1,
"id" => "com.example.post",
"defs" => %{
"main" => %{
"type" => "record",
"record" => %{
"type" => "object",
"required" => ["text", "createdAt"],
"properties" => %{
"text" => %{
"type" => "string",
"maxLength" => 300
},
"createdAt" => %{
"type" => "string",
"format" => "datetime"
}
}
}
}
}
}
# Validate data
data = %{
"text" => "Hello, ATProto!",
"createdAt" => "2024-01-01T00:00:00Z"
}
case Validation.validate(schema, "main", data) do
{:ok, validated_data} ->
# Data is valid and normalized
IO.puts("Valid: #{inspect(validated_data)}")
{:error, message} ->
# Validation failed
IO.puts("Error: #{message}")
endXRPC Endpoint Validation
# Query endpoint schema
query_schema = %{
"lexicon" => 1,
"id" => "com.example.getPosts",
"defs" => %{
"main" => %{
"type" => "query",
"parameters" => %{
"type" => "params",
"properties" => %{
"limit" => %{"type" => "integer", "minimum" => 1, "maximum" => 100}
}
},
"output" => %{
"encoding" => "application/json",
"schema" => %{
"type" => "object",
"properties" => %{
"posts" => %{"type" => "array", "items" => %{"type" => "string"}}
}
}
}
}
}
}
# Validate parameters
{:ok, params} = Validation.validate_parameters(query_schema, "main", %{"limit" => "50"})
# Validate output
{:ok, output} = Validation.validate_output(query_schema, "main", %{
"posts" => ["Post 1", "Post 2"]
})String Format Validation
# DIDs (Decentralized Identifiers)
{:ok, did} = Validation.validate(did_schema, "main", "did:plc:abc123xyz")
# Handles
{:ok, handle} = Validation.validate(handle_schema, "main", "user.bsky.social")
# NSIDs (Namespace IDs)
{:ok, nsid} = Validation.validate(nsid_schema, "main", "com.atproto.server.createSession")
# Datetime (ISO 8601 / RFC 3339)
{:ok, dt} = Validation.validate(datetime_schema, "main", "2024-01-01T12:00:00Z")Supported Types
Primitive Types
string- with optional format validationinteger- with min/max constraintsbooleanunknown- accepts any value
String Formats
datetime- ISO 8601 / RFC 3339uri- Generic URIat-uri- AT Protocol URIdid- Decentralized Identifierhandle- DNS-like handleat-identifier- DID or handlensid- Namespace IDcid- Content Identifierlanguage- BCP 47 language tagtid- Timestamp IDrecord-key- Record key
Complex Types
object- with required/nullable propertiesarray- with min/max lengthunion- with closed/open unionsref- references to other definitions
IPLD Types
bytes- binary data with length constraintscid-link- CID references
Special Types
blob- file uploads with accept types and max sizetoken- opaque token values
Top-Level Types
record- repository recordsquery- XRPC GET endpointsprocedure- XRPC POST endpointssubscription- XRPC WebSocket endpoints
XRPC Validation Functions
AetherLexicon provides dedicated functions for validating different parts of XRPC endpoints:
validate_input/3- Validates request bodyvalidate_output/3- Validates response bodyvalidate_parameters/3- Validates URL/query parametersvalidate_message/3- Validates subscription messagesvalidate_error/4- Validates named error responses
Documentation
Comparison with Official Library
AetherLexicon provides 100% validation parity with the official TypeScript @atproto/lexicon library. In fact, we provide additional functionality (error validation) that the official library doesn't have.
The main difference is architectural:
- Official library: Collection-based API with schema registry
- AetherLexicon: Functional API with direct validation
See docs/OFFICIAL_LEXICON_COMPARISON.md for a detailed comparison.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
Apache License - see LICENSE.md for details.
Acknowledgments
This library implements the ATProto Lexicon specification created by Bluesky PBLLC. The validation logic is based on the official TypeScript implementation at @atproto/lexicon.