Contributing to AT Protocol OCaml#
Thank you for your interest in contributing! This document provides guidelines and instructions for contributing to the AT Protocol OCaml libraries.
Getting Started#
Prerequisites#
- OCaml >= 5.1 (5.4 recommended)
- opam >= 2.0
- dune >= 3.20
Setting Up Development Environment#
# Clone the repository
git clone https://github.com/gdiazlo/atproto.git
cd atproto
# Install dependencies
opam install . --deps-only --with-test
# Build the project
dune build
# Run tests
dune runtest
Project Structure#
atproto/
├── lib/ # Library source code
│ ├── api/ # High-level API client
│ ├── crypto/ # Cryptographic operations
│ ├── effects/ # I/O effect abstractions
│ ├── identity/ # DID/Handle resolution
│ ├── ipld/ # DAG-CBOR, CIDs, CAR files
│ ├── lexicon/ # Schema parsing/validation
│ ├── mst/ # Merkle Search Tree
│ ├── multibase/ # Base encoding
│ ├── repo/ # Repository operations
│ ├── sync/ # Firehose and sync
│ ├── syntax/ # Identifier parsing
│ └── xrpc/ # XRPC client/server
├── test/ # Test suites
│ ├── fixtures/ # Test data (atproto-interop-tests)
│ └── */ # Per-package tests
├── examples/ # Example applications
└── dune-project # Project configuration
Development Workflow#
Running Tests#
# Run all tests
dune runtest
# Run tests for a specific package
dune runtest test/syntax
# Run with verbose output
dune runtest --force --verbose
# Run a specific test file
dune exec test/syntax/test_syntax.exe
Code Formatting#
We use ocamlformat for consistent code style:
# Format all files
dune fmt
# Check formatting without modifying
dune fmt --preview
The project uses the default ocamlformat configuration (see .ocamlformat).
Building Documentation#
# Build API documentation
dune build @doc
# Open in browser
open _build/default/_doc/_html/index.html
Coding Guidelines#
General Principles#
- Functional first - Prefer immutable data structures and pure functions
- Type safety - Use the type system to prevent errors at compile time
- No regex - All syntax validation uses hand-written parsers
- Effects for I/O - Use the effects system for all I/O operations
Module Structure#
Each package follows a consistent structure:
lib/packagename/
├── dune # Build configuration
├── atproto_packagename.ml # Public interface (re-exports)
├── module1.ml # Implementation
├── module1.mli # Interface (optional but recommended)
└── ...
Code Style#
(* Use descriptive names *)
let validate_handle s = ...
(* Document public functions *)
(** [of_string s] parses [s] as a handle.
Returns [Error msg] if the string is not a valid handle. *)
val of_string : string -> (t, string) result
(* Prefer Result over exceptions for expected errors *)
let parse input =
match ... with
| Some v -> Ok v
| None -> Error "invalid input"
(* Use labeled arguments for clarity when appropriate *)
let create ~did ~handle ~service_endpoint = ...
Error Handling#
- Use
Resultfor recoverable errors - Use
Optionfor optional values - Reserve exceptions for programming errors (bugs)
- Provide descriptive error messages
Testing#
- Every module should have corresponding tests
- Use the interop test fixtures where applicable
- Test edge cases and error conditions
- Structure tests with clear descriptions
let test_handle_valid () =
let cases = ["alice.bsky.social"; "test.example.com"] in
List.iter (fun s ->
match Handle.of_string s with
| Ok _ -> ()
| Error e -> Alcotest.fail (Printf.sprintf "%s: %s" s e)
) cases
let () =
Alcotest.run "Handle" [
"parsing", [
Alcotest.test_case "valid handles" `Quick test_handle_valid;
Alcotest.test_case "invalid handles" `Quick test_handle_invalid;
];
]
Making Changes#
Before You Start#
- Check existing issues to avoid duplicate work
- For large changes, open an issue first to discuss the approach
- Make sure you understand the AT Protocol specs at atproto.com
Pull Request Process#
-
Fork and branch - Create a feature branch from
maingit checkout -b feature/my-feature -
Make changes - Follow the coding guidelines above
-
Test - Ensure all tests pass
dune runtest -
Format - Run the formatter
dune fmt -
Commit - Write clear commit messages
Add handle resolution via DNS TXT records Implements DNS-based handle resolution as specified in the AT Protocol identity spec. Includes tests using fixtures from atproto-interop-tests. -
Push and PR - Open a pull request with:
- Clear description of changes
- Reference to related issues
- Test coverage for new functionality
Commit Message Format#
<type>: <short summary>
<detailed description if needed>
<references>
Types:
feat- New featurefix- Bug fixdocs- Documentation onlytest- Adding or updating testsrefactor- Code change that doesn't fix a bug or add a featurechore- Maintenance tasks
Working with the AT Protocol Spec#
Key Resources#
Interop Tests#
The test fixtures in test/fixtures/ come from the official
atproto-interop-tests repository.
To update fixtures:
cd test/fixtures
git pull origin main
When implementing new functionality, check if there are relevant fixtures to test against.
Package-Specific Notes#
atproto-syntax#
- No regex - all parsing is done with hand-written parsers
- Each identifier type has
of_string,to_string, and validation - Follow the exact spec for character sets and lengths
atproto-crypto#
- Uses
mirage-crypto-ecfor P-256 - Uses
secp256k1-mlindirectly via custom K-256 wrapper - Always apply low-S normalization to signatures
- did:key encoding must match multicodec spec exactly
atproto-ipld#
- DAG-CBOR keys must be sorted by length, then lexicographically
- CIDs use SHA-256 and base32lower encoding
- Support both CIDv0 and CIDv1
atproto-effects#
- Define effect types for all I/O operations
- Keep effect handlers separate from business logic
- Test with mock handlers, deploy with real handlers
Questions?#
- Open an issue for questions about the codebase
- Check the AT Protocol Discord for protocol questions
- Review the reference TypeScript implementation for clarification
License#
By contributing, you agree that your contributions will be licensed under the MIT License.