This document captures code conventions for the dropshot-api-manager project. It is intended to help LLMs understand how to work effectively with this codebase.
- Model the full error space—no shortcuts or simplified error handling.
- Handle and test for all edge cases.
- Use the type system to encode correctness constraints.
- Prefer compile-time guarantees over runtime checks where possible.
- Provide structured, helpful error messages using
anyhowwith rich context chains. - Make progress reporting responsive and informative (e.g.,
Checking,Fresh,Staleheaders). - Maintain consistency across platforms even when underlying OS capabilities differ.
- Evolve the design incrementally rather than attempting perfect upfront architecture.
- Document design decisions and trade-offs in code comments.
- Use type system extensively: newtypes, builder patterns, type states, lifetimes.
- Test comprehensively, including edge cases.
- Pay attention to what facilities already exist for testing, and aim to reuse them.
- Getting the details right is really important!
- Use inline comments to explain "why," not "what."
- Module-level documentation should explain purpose and responsibilities.
- Always use periods at the end of code comments.
- Never use title case in headings and titles. Always use sentence case.
- Wrap comments to 80 characters.
Every Rust source file must start with:
// Copyright (current year) Oxide Computer CompanyImportant: When editing a file, compare the current year to the year in the copyright header. If it is different, update the copyright header to the current year.
- Use Rust 2024 edition.
- Format with
cargo xfmt(custom formatting script). - Formatting is enforced in CI—always run
cargo xfmtbefore committing.
- Newtypes for domain types (e.g.,
ApiIdent,VcsRevision,GitCommitHash,ApiSpecFileName) - Builder patterns for complex construction (e.g.,
ManagedApiwithwith_extra_validation,with_git_stub_storage) - Type states encoded in generics when state transitions matter
- Lifetimes used extensively to avoid cloning (e.g.,
VersionProblem<'a>,Resolution<'a>,Fix<'a>) - Restricted visibility: Use
pub(crate)andpub(super)liberally - Non-exhaustive: Consider forward compatibility for public error types
- Use
thiserrorfor error types with#[derive(Error)]. - Group errors by category with specific enums (e.g.,
VersionProblem,NonVersionProblem,Note). - Provide rich error context using
anyhow::Context. - Two-tier model:
- Problems: User-visible issues that may be fixable or unfixable.
- Internal errors: Programming errors that may panic or use internal error types.
- Error display messages should be lowercase sentence fragments suitable for "failed to {error}".
- Errors should explain: What happened? Why? How to fix it?
- Use
mod.rsfiles to re-export public items. - Do not put any nontrivial logic in
mod.rs—instead, it should go in specific submodules. - Keep module boundaries strict with restricted visibility.
- Platform-specific code in separate files:
unix.rs,windows.rs. - Use
#[cfg(unix)]and#[cfg(windows)]for conditional compilation. - Test helpers in dedicated modules/files (e.g.,
test_util,integration-testscrate).
- Use
Arcor borrows for shared immutable data. - Use
&'static strwhen appropriate. - Careful attention to copying vs. referencing.
- Use
debug-ignoreto hide irrelevant information. - Use iterators where possible rather than buffering.
CRITICAL: Always use cargo nextest run to run unit and integration tests. Never use cargo test for these! This project uses nextest for its execution model, including process isolation which makes it safe to alter environment variables within tests.
For doctests, use cargo test --doc (doctests are not supported by nextest).
Since this repository uses nextest, which is process-per-test, it is safe to alter the environment within tests. Whenever you do that, add to the unsafe block:
// SAFETY:
// https://nexte.st/docs/configuration/env-vars/#altering-the-environment-within-tests- Unit tests in the same file as the code they test.
- Integration tests in the
integration-tests/crate. - Test fixtures in
integration-tests/src/fixtures.rs.- This file provides model APIs for common test scenarios (lockstep, versioned, git-stub).
- Prefer using these fixtures over implementing spot checks by hand.
- Test utilities in
test_utilmodule within the main crate.
- assert_matches: For matching enum variants in assertions.
- camino-tempfile: For temporary file/directory operations with UTF-8 paths.
- camino-tempfile-ext: Extensions for camino-tempfile.
The dropshot-api-manager manages OpenAPI documents corresponding to Dropshot API traits. It supports:
- Lockstep versioning: Clients and servers always match; single OpenAPI document per API.
- Versioned APIs: Multiple versions supported simultaneously; enables online upgrades where clients and servers can be temporarily mismatched.
The tool reconciles OpenAPI documents from three sources:
-
Blessed source: Immutable upstream versions from VCS history (typically the merge-base with main). They form a source of truth. These represent committed/shipped API documents that cannot be changed incompatibly.
-
Generated source: Documents generated fresh from the current API trait definitions. Rust code is the source for these.
-
Local source: Documents in the working tree. These may be blessed versions, or locally-added versions being developed in a branch.
For blessed versions, the corresponding API documents are the source of truth (authoritative). The API manager ensures that the Rust API traits are wire-compatible with these.
For locally-added versions, the Rust API trait is the source of truth. Once a version is committed in main, the document becomes the source of truth.
pub enum Versions {
Lockstep { version: semver::Version },
Versioned { supported_versions: SupportedVersions },
}For versioned APIs, use the api_versions! macro to define supported versions:
api_versions!([
(3, WITH_METRICS),
(2, WITH_DETAILED_STATUS),
(1, INITIAL),
]);This defines constants VERSION_WITH_METRICS, VERSION_WITH_DETAILED_STATUS, VERSION_INITIAL and functions supported_versions(), latest_version().
The Resolved type represents the result of comparing blessed, generated, and local sources:
pub enum ResolutionKind {
Lockstep, // Single-version API.
Blessed, // Versioned, present in upstream.
NewLocally, // Versioned, only in current branch.
}
// Per-(api, version) problems: anything tied to a specific supported
// version. The bulk of the variants live here.
pub enum VersionProblem<'a> {
BlessedVersionMissingLocal { ... },
BlessedVersionBroken { ... },
LockstepStale { ... },
LocalVersionMissingLocal { ... },
// ... more variants
}
// Problems not tied to a specific version: orphaned files, unparseable
// files, and the "latest" symlink for versioned APIs.
pub enum NonVersionProblem<'a> {
LocalSpecFileOrphaned { ... },
UnparseableLocalFile { ... },
LatestLinkMissing { ... },
LatestLinkStale { ... },
}Both problem enums are either fixable (tool can auto-correct) or unfixable (require manual intervention). Splitting per-version problems out lets rendering code that needs per-(api, version) context (e.g., compatibility-issue dedup) require it at the type level.
-
Three-way reconciliation—blessed, generated, and local sources are compared to detect drift and ensure compatibility.
-
Wire compatibility checking—uses the
driftcrate to semantically compare OpenAPI documents. Trivial changes (doc updates, type renames) are allowed in blessed versions; semantic changes (forward compatible, backwards compatible, or incompatible) are not. -
Atomic file operations—uses
atomicwritescrate to prevent corruption on interruption. -
VCS integration—blessed versions are loaded from VCS history (Git or Jujutsu). The
RepoVcsabstraction (vcs.rs) wraps both backends, with Git-specific operations ingit.rsand Jujutsu-specific operations injj.rs. Git stub storage optionally stores older versions as.gitstubfiles containing commit references rather than full JSON. -
UTF-8 paths throughout—uses
caminocrate (Utf8Path,Utf8PathBuf) for easier path handling.
crates/
├── dropshot-api-manager/ # Main implementation
│ ├── src/
│ │ ├── lib.rs # Public API exports
│ │ ├── apis.rs # ManagedApiConfig, ManagedApi, ManagedApis
│ │ ├── cmd/ # CLI commands (dispatch, check, generate, list, debug)
│ │ ├── environment.rs # Environment configuration and resolution
│ │ ├── resolved.rs # Resolution logic, VersionProblem / NonVersionProblem / Fix enums
│ │ ├── compatibility/ # Wire compatibility checking via drift
│ │ │ ├── mod.rs # Module re-exports
│ │ │ ├── types.rs # ApiCompatIssue and related data model
│ │ │ ├── detect.rs # Bridge from drift output into the data model
│ │ │ └── display.rs # Styled CLI rendering of compatibility issues
│ │ ├── validation.rs # OpenAPI document validation
│ │ ├── vcs/ # VCS abstraction (RepoVcs: Git/Jujutsu dispatch)
│ │ │ ├── mod.rs # Module re-exports
│ │ │ ├── imp.rs # RepoVcs, VcsRevision, command helpers
│ │ │ ├── git.rs # Git-specific operations
│ │ │ └── jj.rs # Jujutsu-specific operations
│ │ ├── output.rs # User-facing output formatting
│ │ ├── spec_files_blessed.rs # Blessed source file handling
│ │ ├── spec_files_generated.rs # Generated source file handling
│ │ ├── spec_files_local.rs # Local source file handling
│ │ └── test_util/ # Test utilities
│ └── Cargo.toml
├── dropshot-api-manager-types/ # Core types (minimal deps, for API crates to depend on)
│ └── src/
│ ├── lib.rs
│ ├── apis.rs # ApiIdent, ApiSpecFileName
│ ├── validation.rs # ValidationContext, ValidationBackend
│ └── versions.rs # Versions, SupportedVersions, api_versions! macro
└── integration-tests/ # Integration test suite
├── src/
│ ├── lib.rs
│ ├── fixtures.rs # Test API fixtures (lockstep, versioned, etc.)
│ └── environment.rs # Test environment utilities
└── tests/
└── integration/ # Integration tests
- Unix: Symlinks for "latest" version pointers, standard file operations.
- Windows: Requires developer mode for symlinks, CRLF conversion disabled via
.gitattributes. - Conditional compilation:
#[cfg(unix)],#[cfg(windows)]. - Document platform differences and trade-offs in code comments.
- All versions managed in root
Cargo.toml[workspace.dependencies]. - Comment on dependency choices when non-obvious.
- dropshot: Dropshot HTTP framework and API description generation.
- openapiv3: OpenAPI document parsing and representation.
- drift: Semantic diff of OpenAPI documents for compatibility checking.
- thiserror: Error derive macros.
- anyhow: Error handling and context.
- camino: UTF-8 paths (
Utf8PathBuf). - serde_json: JSON manipulation.
- semver: Version parsing and comparison.
- clap: CLI parsing with derives.
- atomicwrites: Safe atomic file writing.
- fs-err: Better filesystem error messages.
- sha2: Hashing for versioned spec filenames.
- owo-colors: Colored terminal output.
- similar: Diff generation for display.
- supports-color: Terminal color capability detection.
- openapi-lint: Custom linting for OpenAPI documents.
# Run tests (ALWAYS use nextest for unit/integration tests)
cargo nextest run
cargo nextest run --all-features
# Run doctests (nextest doesn't support these)
cargo test --doc
# Format code (REQUIRED before committing)
cargo xfmt
# Before pushing, run all of these — they are checked in CI:
cargo clippy --all-features --all-targets
just rustdoc
# Build
cargo build --all-targets --all-features
# Run the e2e example
cargo example-openapi list
cargo example-openapi check
cargo example-openapi generate0(SUCCESS): All checks passed.4(NEEDS_UPDATE): Documents are stale and need regeneration.100(FAILURE): Validation errors or unfixable problems.
- Create the API trait crate.
- Add to
ManagedApis::new()withVersions::Lockstep { version: "1.0.0".parse().unwrap() }. - Run
cargo openapi generate.
- Create the API trait crate with
api_versions!macro. - Add to
ManagedApis::new()withVersions::Versioned { supported_versions: my_api::supported_versions() }. - Run
cargo openapi generate.
- Add a new version to
api_versions!(first position = latest). - Annotate changed endpoints with
versions = VERSION_NEW..orversions = ..VERSION_NEW. - Run
cargo openapi generate.
- Remove the "latest" symlink if conflicted.
- Fix up the
api_versions!call (take all upstream versions, renumber your local version). - Run
cargo openapi generate.
Commits follow a conventional format with crate-specific scoping:
[crate-name] brief description
Examples:
[dropshot-api-manager] add Git stub storage for older API versions[dropshot-api-manager-types] version 0.3.0[meta] prepare changelog
- Use
[meta]for cross-cutting concerns (releases, CI changes). - Version bump commits:
[crate-name] version X.Y.Z. These are generated by cargo-release. - Keep descriptions concise but descriptive.
- Atomic commits: Each commit should be a logical unit of change.
- Bisectable history: Every commit must build and pass all checks.
- Separate concerns: Format fixes and refactoring should be in separate commits from feature changes.