Imported from terjekv/mreg-rust (
AGENTS.md). Install upstream withnpx skills add terjekv/mreg-rust. Copyright stays with the author.
AGENTS.md
This file provides guidance to coding agents when working with code in this repository.
Project
Rust reimplementation of the Django-based mreg — a DNS and network inventory management REST API. Manages zones, hosts, DNS records (18 built-in types with RFC validation), networks, and related infrastructure. Uses Rust 2024 edition.
Rust Standards
- Follow Rust best practices and the conventions already present in this repository.
- Prefer designs built around newtypes instead of passing primitive values through the domain unchecked.
- Newtypes should usually have validating constructors, private fields, and explicit accessors or setters where mutation is part of the model.
- Endpoints should accept newtypes whenever possible so validation happens at the boundary, as early as possible, with clear and actionable error messages.
- Put behavior on types with
implblocks when it naturally belongs to the type. Prefer this over collections of bare functions that operate on loosely related data. - Keep invariants close to the data they protect. Constructors and setters should reject invalid states rather than relying on callers to remember preconditions.
- Use small, explicit APIs. Expose only what callers need, and keep representation details private unless there is a strong reason not to.
- Prefer
useimports over inline fully qualified paths for functions, types, and macros. Only fully qualify a path inline when needed to resolve a genuine name ambiguity, or for a one-off reference where ausewould mislead. - Prefer one assertion per test. Use
rstestcases for multiple inputs or outcomes, and split unrelated response properties into separate tests unless they form one cohesive assertion.
Commands
cargo build # Build
cargo run # Run server (localhost:8080, Swagger at /swagger-ui/)
cargo test # Run tests (memory-only, postgres tests skipped)
# With PostgreSQL (required for full test suite):
MREG_TEST_DATABASE_URL="postgres://mreg:mreg@localhost:5433/mreg_test" cargo test
MREG_TEST_DATABASE_URL="postgres://mreg:mreg@localhost:5433/mreg_test" cargo test --test dual_backend
MREG_TEST_DATABASE_URL="postgres://mreg:mreg@localhost:5433/mreg_test" cargo test --test postgres_storage
MREG_TEST_DATABASE_URL="..." cargo test test_name_here # Single test
MREG_TEST_DATABASE_URL="..." cargo test test_name -- --nocapture # With output
# Database migrations (requires diesel_cli)
DATABASE_URL="postgres://mreg:mreg@localhost:5433/mreg" diesel migration run
Architecture
Five-layer design with pluggable storage backends:
src/api/v1/ → Actix-web HTTP handlers, request/response DTOs, OpenAPI via utoipa
src/authn/ → Authentication providers (none/forward/LDAP), JWT issuing/validation
src/services/ → Audit recording + event emission on mutations, delegates to storage
src/storage/ → Storage trait definitions + two backend implementations
storage/memory/ → In-memory HashMap backend (for tests)
storage/postgres/→ Diesel ORM backend (for production)
src/domain/ → Value objects, entities, commands, validation (transport-agnostic)
src/db/ → Diesel schema.rs (auto-generated), models.rs (row types)
Data flow per request: API handler → parses request into domain command → calls service → service delegates to storage trait → backend executes → service records audit event + emits domain event → API handler maps to response DTO.
Key design patterns
Type-driven domain: All domain invariants are value objects in src/domain/types.rs (DnsName, Hostname, ZoneName, LabelName, CidrValue, etc.). They validate and normalize on construction (e.g., DNS names lowercased, trailing dots stripped). Private fields, no mutation — only new(), restore(), and accessor methods. Custom Serialize/Deserialize impls that go through validation.
Storage trait composition: The Storage trait in src/storage/mod.rs aggregates ~18 subsystem store traits (LabelStore, ZoneStore, HostStore, RecordStore, etc.). Each store trait defines CRUD + listing with filtering/pagination. Both backends implement all traits. Backend selected at runtime via MREG_STORAGE_BACKEND env var (auto/memory/postgres).
Atomic cascading side-effects: Operations like record cleanup, zone serial bumps, and auto-created PTR records happen inside storage implementations, not orchestrated by services.
Unified errors: AppError enum in src/errors.rs maps variants to HTTP status codes (Validation→400, NotFound→404, Conflict→409, etc.). All errors serialize to { error: "kind", message: "details" }.
Cursor-based pagination: PageRequest/Page<T> in src/domain/pagination.rs. The page_response! macro generates utoipa-visible wrappers for each entity type.
Service-layer audit + events: Audit recording and event emission are enforced at the service layer (src/services/), not inside storage backends. This guarantees every mutation is audited and emits a DomainEvent regardless of backend. Events are fire-and-forget via EventSink trait (src/events/), with webhook, AMQP, and Redis backends. AMQP and Redis are behind feature flags. See docs/event-system.md.
Authentication: Configurable via MREG_AUTH_MODE (none/scoped). In none mode, identity is trusted from X-Mreg-User/X-Mreg-Groups headers. In scoped mode, a provider registry owns named local, remote, and LDAP providers; login selects one with a separate identity_scope field and issues an mreg JWT. Actix middleware validates bearer tokens and populates PrincipalContext in request extensions. extract_principal() reads from extensions first, falling back to headers in none mode. Token revocation is supported via AuthSessionStore. See docs/authentication.md.
Structured logging: Uses tracing with per-request spans containing request_id, principal, http.method, http.target, http.status_code. Service functions are instrumented with #[tracing::instrument] and resource_kind fields. Errors are logged automatically (WARN for 4xx, ERROR for 5xx). JSON output via MREG_JSON_LOGS=true. See docs/logging.md.
Adding a new entity
Follow the existing pattern across all layers (use labels as the simplest example):
- Domain entity + commands:
src/domain/foo.rs - Storage trait:
src/storage/foo.rs - Memory backend:
src/storage/memory/foo.rs - Postgres backend:
src/storage/postgres/foo.rs - Service:
src/services/foo.rs - API handler:
src/api/v1/foo.rs - Register in
src/api/mod.rs(OpenAPI paths + schemas) andsrc/api/v1/mod.rs(routes) - Register store trait in
src/storage/mod.rsand both backend mod.rs files - DB migration in
migrations/(schema.rs auto-regenerates), model insrc/db/models.rs
Testing
Dual-backend conformance tests (tests/dual_backend.rs): The dual_backend_test! macro generates both test_name::memory and test_name::postgres variants from a single scenario function. Postgres tests are skipped when MREG_TEST_DATABASE_URL is not set.
TestCtx (tests/common/mod.rs): Provides namespaced test data (names, zones, CIDRs) so tests can run in parallel against a shared database without conflicts. Includes HTTP helpers (get_json, post, patch, delete) and seed methods (seed_zone, seed_host, seed_network).
Other test files: api_contract_memory.rs (API contract tests), rr_validation.rs (DNS record validation), filter_*.rs (filtering), pagination.rs, sorting.rs.
Configuration
Environment variables (see .env.example). Key ones: MREG_DATABASE_URL, MREG_TEST_DATABASE_URL, MREG_STORAGE_BACKEND (auto/memory/postgres), MREG_LISTEN, MREG_PORT, MREG_ALLOW_DEV_AUTHZ_BYPASS. Full reference in src/config.rs and docs/configuration.md.
Documentation
Detailed guides in docs/ covering architecture, API differences from Django mreg, storage layer, type-driven design, pagination/filtering, DNS record standards, host policy, export templating, and more.
Pull Requests And Merges
- Treat the changelog review as required for every pull request. Before merge, add relevant user-facing additions, changes, fixes, and security notes to the [Unreleased] section of CHANGELOG.md. If a pull request has no changelog-worthy impact, state that explicitly in its description; do not add empty or internal-only changelog entries.
- Call out every breaking change explicitly in both the pull request description and its [Unreleased] changelog entry, including the upgrade or migration action users must take.
- When squash-merging a pull request, use its detailed PR description as the squash commit body. Preserve the substantive summary, rationale, behavior notes, and issue references, but remove verification-only sections such as test commands, checklists, and ## Verification before merging.