diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..fd60f61 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,59 @@ +--- +name: Bug Report +about: Report a bug or unexpected behavior +title: "[BUG] " +labels: bug +assignees: '' +--- + +## Description + +A clear and concise description of the bug. + +## To Reproduce + +Steps to reproduce the behavior: + +1. Create error with '...' +2. Call method '...' +3. Observe '...' + +## Minimal Reproducible Example + +```rust +use masterror::prelude::*; + +fn main() { + // Minimal code that reproduces the issue +} +``` + +## Expected Behavior + +A clear description of what you expected to happen. + +## Actual Behavior + +A clear description of what actually happened. + +## Error Messages + +``` +Paste any error messages, stack traces, or panic output here. +``` + +## Environment + +- **masterror version**: [e.g., 0.24.19] +- **Rust version**: [output of `rustc --version`] +- **Operating System**: [e.g., Linux 5.15, macOS 13.2, Windows 11] +- **Features enabled**: [e.g., "std", "axum", "sqlx"] +- **Additional context**: [Any other relevant information] + +## Possible Solution + +If you have ideas on how to fix the issue, describe them here. + +## Additional Context + +Add any other context about the problem here (related issues, workarounds, etc.). diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 0000000..798e7d7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,58 @@ +--- +name: Documentation Improvement +about: Suggest improvements to documentation +title: "[DOCS] " +labels: documentation +assignees: '' +--- + +## Documentation Issue + +**Type**: (check one) +- [ ] Missing documentation +- [ ] Incorrect documentation +- [ ] Unclear documentation +- [ ] Outdated documentation +- [ ] Example needed + +## Location + +**Affected file(s)**: +- [ ] README.md +- [ ] CHANGELOG.md +- [ ] ARCHITECTURE.md +- [ ] REQUIREMENTS.md +- [ ] CONTRIBUTING.md +- [ ] docs.rs API documentation +- [ ] docs/wiki/ +- [ ] Other: ___________ + +**Specific section**: (e.g., "Installation" section in README, `AppError::with_field` doc comment) + +## Current State + +Describe what the current documentation says or what's missing. + +``` +Quote the current text here, if applicable. +``` + +## Proposed Improvement + +Describe what should be changed, added, or clarified. + +``` +Provide the proposed text or example here. +``` + +## Rationale + +Why is this improvement needed? What confusion does it resolve? + +## Related Issues + +Are there existing issues or PRs related to this documentation? + +## Additional Context + +Add any other context, screenshots, or examples about the documentation issue here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..04aceee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,57 @@ +--- +name: Feature Request +about: Suggest a new feature or enhancement +title: "[FEATURE] " +labels: enhancement +assignees: '' +--- + +## Feature Description + +A clear and concise description of the feature you'd like to see. + +## Use Case + +Describe the problem this feature would solve or the workflow it would improve. + +**Current behavior**: +What do you have to do now to accomplish this task? + +**Desired behavior**: +How would this feature make the task easier or better? + +## Proposed API (if applicable) + +```rust +// Example of how the feature might be used +use masterror::prelude::*; + +fn example() { + // Show the proposed API usage +} +``` + +## Alternatives Considered + +Describe alternative solutions or features you've considered. + +## Impact Assessment + +- **Breaking changes**: Yes / No (describe if yes) +- **Performance impact**: None / Positive / Negative (explain) +- **Compatibility**: What versions would this affect? +- **Dependencies**: Would this require new dependencies? + +## Implementation Notes + +If you have ideas about how to implement this feature, describe them here. + +## Willingness to Contribute + +- [ ] I am willing to submit a PR for this feature +- [ ] I need guidance on implementation +- [ ] I can only provide requirements/testing + +## Additional Context + +Add any other context, mockups, or examples about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c8616d5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,58 @@ + + +## Summary + +Brief description of what this PR accomplishes. + +## Changes + +- Added: ... +- Fixed: ... +- Changed: ... +- Removed: ... + +## Motivation + +Why is this change necessary? What problem does it solve? + +## Test Plan + +- [ ] Unit tests pass locally (`cargo test --all-features`) +- [ ] Integration tests pass locally +- [ ] Doctests compile and run +- [ ] Benchmarks run without regression (`cargo bench --features benchmarks`) +- [ ] Code formatted with nightly fmt (`cargo +nightly fmt --all -- --check`) +- [ ] Clippy passes with no warnings (`cargo clippy --all-targets --all-features -- -D warnings`) +- [ ] Documentation builds (`cargo doc --no-deps --all-features`) +- [ ] Manual testing performed (describe if applicable) + +## Performance Impact + +- [ ] No performance impact +- [ ] Performance improved (attach benchmark results) +- [ ] Performance regression justified (explain rationale) + +## Breaking Changes + +- [ ] No breaking changes +- [ ] Breaking changes (document migration path in CHANGELOG.md) + +## Documentation + +- [ ] Updated README.md (if public API changed) +- [ ] Updated CHANGELOG.md under "Unreleased" section +- [ ] Added/updated doc comments for public APIs +- [ ] Added/updated examples (if applicable) +- [ ] Updated migration guides (if breaking changes) + +## Related Issues + +Closes # + +## Additional Context + +Any additional information reviewers should know. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..2d9e5ee --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,699 @@ + + +# Architecture Documentation + +## Table of Contents + +1. [High-Level Design](#high-level-design) +2. [Module Structure](#module-structure) +3. [Core Types](#core-types) +4. [Data Flow](#data-flow) +5. [Extension Points](#extension-points) +6. [Performance Characteristics](#performance-characteristics) +7. [Design Patterns](#design-patterns) +8. [Compile-Time Guarantees](#compile-time-guarantees) + +## High-Level Design + +### Design Philosophy + +This architecture adheres to the [RustManifest](https://github.com/RAprogramm/RustManifest) principles, emphasizing clean code, zero-cost abstractions, comprehensive testing, and professional documentation standards. + +masterror follows a **layered architecture** with clear separation between: + +1. **Core layer**: Framework-agnostic error types and metadata +2. **Conversion layer**: Integration with third-party libraries +3. **Transport layer**: HTTP, gRPC, and serialization adapters +4. **Derive layer**: Procedural macros for ergonomic derivation +5. **Turnkey layer**: Opinionated defaults for rapid adoption + +``` +┌─────────────────────────────────────────────────────────┐ +│ Application Code │ +└────────────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Turnkey Layer (Optional) │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Pre-built catalog, classifiers, helper functions │ │ +│ └──────────────────────────────────────────────────┘ │ +└────────────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Derive Layer │ +│ ┌────────────────┬─────────────────┬─────────────┐ │ +│ │ #[derive(Error)]│ #[derive(Master-│ #[provide] │ │ +│ │ │ ror)] │ │ │ +│ └────────────────┴─────────────────┴─────────────┘ │ +└────────────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Transport Layer │ +│ ┌──────────┬───────────┬─────────┬──────────────┐ │ +│ │ Axum │ Actix │ Tonic │ OpenAPI │ │ +│ │ Responder│ Responder │ Status │ Schema Gen │ │ +│ └──────────┴───────────┴─────────┴──────────────┘ │ +└────────────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Conversion Layer │ +│ ┌───────┬────────┬───────┬────────┬─────────────┐ │ +│ │ sqlx │reqwest │ redis │tokio │ validator │ │ +│ │ │ │ │ │ ... │ │ +│ └───────┴────────┴───────┴────────┴─────────────┘ │ +└────────────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Core Layer │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ AppError │ AppErrorKind │ AppCode │ Metadata │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### Workspace Organization + +``` +masterror/ +├── masterror/ # Main crate (re-exports all layers) +├── masterror-derive/ # Procedural macros +└── masterror-template/ # Template parser (used by derive) +``` + +**Dependency direction**: `masterror` → `masterror-derive` → `masterror-template` + +Derive macros expand to code that uses `masterror` public API, creating a build-time dependency cycle handled by Cargo's macro expansion phase. + +## Module Structure + +### Core Modules + +#### `src/lib.rs` +Entry point, re-exports public API, manages feature flags. + +#### `src/app_error/` +Core error type implementation. + +- **`core.rs`**: `AppError` struct definition and `std::error::Error` impl +- **`constructors.rs`**: Convenience constructors (`AppError::internal`, `::not_found`, etc.) +- **`metadata.rs`**: `Metadata` and `Field` types for structured context +- **`context.rs`**: `Context` builder for attaching metadata + +#### `src/kind.rs` +`AppErrorKind` enum mapping to HTTP 4xx/5xx classes and internal categories. + +```rust +pub enum AppErrorKind { + BadRequest, // 400 + Unauthorized, // 401 + Forbidden, // 403 + NotFound, // 404 + Conflict, // 409 + Timeout, // 408 + Service, // 502/503 + Internal, // 500 + // ... +} +``` + +#### `src/code.rs` and `src/code/app_code.rs` +`AppCode` enum for fine-grained classification (100+ variants). + +```rust +pub enum AppCode { + BadRequest, + InvalidFormat, + MissingField, + Unauthorized, + TokenExpired, + // ... +} +``` + +#### `src/mapping.rs` +Transport mapping definitions (`HttpMapping`, `GrpcMapping`, `ProblemMapping`). + +### Conversion Layer + +#### `src/convert/` +Third-party error conversions via `From` trait impls. + +- **`sqlx.rs`**: Database errors → `AppErrorKind::Conflict`, `::Service`, `::Internal` +- **`reqwest.rs`**: HTTP client errors → `AppErrorKind::Service`, `::Timeout` +- **`redis.rs`**: Redis errors → `AppErrorKind::Service`, `::Internal` +- **`tokio.rs`**: Async runtime errors → `AppErrorKind::Internal`, `::Timeout` +- **`validator.rs`**: Validation errors → `AppErrorKind::BadRequest` +- **`config.rs`**: Configuration errors → `AppErrorKind::Internal` +- **`teloxide.rs`**: Telegram bot errors → `AppErrorKind::Service`, `::BadRequest` +- **`multipart.rs`**: Multipart form errors → `AppErrorKind::BadRequest` + +Each conversion: +1. Maps error variant to `AppErrorKind` +2. Preserves source error chain +3. Attaches relevant telemetry (e.g., SQL constraint name, HTTP status code) + +### Transport Layer + +#### `src/response/` +HTTP and serialization adapters. + +- **`core.rs`**: `ErrorResponse` trait for framework-agnostic responses +- **`problem_json.rs`**: RFC 7807 Problem Details serialization +- **`axum_impl.rs`**: `impl IntoResponse for AppError` (Axum) +- **`actix_impl.rs`**: `impl ResponseError for AppError` (Actix-web) +- **`mapping.rs`**: HTTP status code and gRPC status mappings +- **`metadata.rs`**: Metadata serialization with redaction + +#### `src/convert/tonic.rs` +`impl From for tonic::Status` with gRPC code mapping. + +#### `src/convert/axum.rs` and `src/convert/actix.rs` +Extended Axum/Actix integrations beyond basic `IntoResponse`. + +### Derive Macros + +Located in `masterror-derive/src/`: + +- **`error_derive.rs`**: `#[derive(Error)]` implementation +- **`error_trait.rs`**: Trait generation and method synthesis +- **`masterror_derive.rs`**: `#[derive(Masterror)]` with telemetry and redaction +- **`provide_derive.rs`**: `#[provide]` for `std::error::Request` providers + +### Turnkey Module + +#### `src/turnkey/` +Opinionated defaults for rapid adoption. + +- **`domain.rs`**: Pre-built domain error types +- **`classifier.rs`**: Error classification heuristics +- **`conversions.rs`**: Automatic conversions with telemetry + +### Utility Modules + +#### `src/macros.rs` +`ensure!` and `fail!` macros for control flow. + +#### `src/prelude.rs` +Commonly used types for glob imports. + +#### `src/result_ext.rs` +`ResultExt` trait for ergonomic error context attachment. + +#### `src/frontend/` +WASM/browser compatibility. + +- **`browser_console_error.rs`**: `console.error()` integration +- **`browser_console_ext.rs`**: Trait extensions for browser logging + +## Core Types + +### `AppError` + +```rust +pub struct AppError { + pub kind: AppErrorKind, + pub code: AppCode, + pub message: String, + pub edit_policy: MessageEditPolicy, + metadata: Metadata, + source: Option>, + backtrace: Option, + retry_after: Option, + www_authenticate: Option, +} +``` + +**Invariants**: +- `kind` and `code` must be semantically consistent (enforced by constructors) +- `source` chain is immutable after construction +- `metadata` is append-only (fields can be added but not removed) +- `message` is either user-facing or internal based on `edit_policy` + +### `Metadata` + +```rust +pub struct Metadata { + fields: Vec, +} + +pub enum Field { + Str { key: &'static str, value: String, policy: RedactionPolicy }, + I64 { key: &'static str, value: i64, policy: RedactionPolicy }, + U64 { key: &'static str, value: u64, policy: RedactionPolicy }, + F64 { key: &'static str, value: f64, policy: RedactionPolicy }, + Duration { key: &'static str, value: Duration, policy: RedactionPolicy }, + IpAddr { key: &'static str, value: IpAddr, policy: RedactionPolicy }, + Json { key: &'static str, value: Value, policy: RedactionPolicy }, +} +``` + +**Invariants**: +- Keys are static strings (zero allocation overhead) +- Fields are ordered by insertion +- Redaction policy is immutable per field +- No duplicate keys (last insert wins) + +### `Context` + +Builder for attaching metadata: + +```rust +pub struct Context { + kind: AppErrorKind, + code: AppCode, + message: String, + fields: Vec, +} +``` + +**Usage**: +```rust +AppError::internal("db error") + .with_field(field::str("table", "users")) + .with_field(field::duration("query_time", elapsed)) +``` + +## Data Flow + +### Error Creation Flow + +``` +User Code + │ + ├─ AppError::new(kind, message) + │ │ + │ └─> AppError { kind, code: default(kind), message, ... } + │ + └─ AppError::internal(message).with_field(...) + │ + └─> Context::new(kind, message) + │ + └─> Context::with_field(field) + │ + └─> Context::into_error() + │ + └─> AppError { ..., metadata } +``` + +### Third-Party Error Conversion Flow + +``` +sqlx::Error + │ + └─ From for AppError + │ + ├─ match error_variant: + │ ├─ Database constraint → AppErrorKind::Conflict + │ ├─ Connection error → AppErrorKind::Service + │ └─ Query error → AppErrorKind::Internal + │ + ├─ Extract metadata (table, constraint, etc.) + │ + └─> AppError { + kind: ..., + code: ..., + message: ..., + source: Some(sqlx_error), + metadata: [...] + } +``` + +### HTTP Response Flow + +``` +AppError + │ + └─ axum::IntoResponse::into_response() + │ + ├─ ProblemJson::from_app_error() + │ │ + │ ├─ Map kind → HTTP status + │ ├─ Serialize metadata (apply redaction) + │ └─> RFC 7807 JSON payload + │ + ├─ Build headers + │ ├─ Content-Type: application/problem+json + │ ├─ Retry-After: ... (if set) + │ └─ WWW-Authenticate: ... (if set) + │ + └─> axum::Response +``` + +### Derive Macro Expansion Flow + +``` +#[derive(Error)] +#[error("db error: {source}")] +struct DbError { + #[source] + source: io::Error, +} + + │ (proc macro expansion) + ▼ + +impl fmt::Display for DbError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "db error: {}", self.source) + } +} + +impl std::error::Error for DbError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} +``` + +## Extension Points + +### 1. Custom Error Types + +Implement `From for AppError`: + +```rust +impl From for AppError { + fn from(err: MyDomainError) -> Self { + match err { + MyDomainError::NotFound(id) => { + AppError::not_found("resource missing") + .with_field(field::str("resource_id", id)) + } + MyDomainError::InvalidInput(msg) => { + AppError::bad_request(msg) + } + } + } +} +``` + +### 2. Custom Metadata Fields + +Define domain-specific field builders: + +```rust +pub mod field { + pub fn user_id(value: impl Into) -> Field { + Field::str_redacted("user_id", value, RedactionPolicy::Hash) + } + + pub fn transaction_amount(cents: i64) -> Field { + Field::i64("transaction_cents", cents, RedactionPolicy::None) + } +} +``` + +### 3. Custom Transport Mappings + +Override default mappings via derive attributes: + +```rust +#[derive(Masterror)] +#[masterror( + category = AppErrorKind::Service, + map.grpc = 14, // UNAVAILABLE + map.problem = "https://api.example.com/errors/db-unavailable" +)] +struct DatabaseUnavailable; +``` + +### 4. Custom Redaction Policies + +Implement `RedactionPolicy` trait: + +```rust +impl RedactionPolicy { + pub fn custom_mask(&self, value: &str) -> String { + match self { + RedactionPolicy::Custom => mask_pii(value), + _ => self.apply_default(value), + } + } +} +``` + +### 5. Custom Telemetry Providers + +Implement `std::error::Request` providers: + +```rust +#[derive(Error)] +#[error("telemetry snapshot")] +struct MyError { + #[provide(ref = TelemetrySnapshot)] + snapshot: TelemetrySnapshot, +} +``` + +## Performance Characteristics + +### Allocation Patterns + +**Zero-allocation paths**: +- Error kind/code classification +- Static message errors (`AppError::internal("static")`) +- Metadata field key storage (uses `&'static str`) + +**Single-allocation paths**: +- Dynamic message errors (allocates `String`) +- Metadata field value storage (one allocation per field) + +**Multiple-allocation paths**: +- Source error boxing (unavoidable for trait objects) +- Backtrace capture (when enabled) +- RFC 7807 JSON serialization + +### Time Complexity + +- `AppError::new()`: **O(1)** +- `with_field()`: **O(1)** amortized (Vec::push) +- `From`: **O(1)** to **O(k)** where k = number of fields attached +- `ProblemJson::from_app_error()`: **O(n)** where n = number of metadata fields +- HTTP response generation: **O(n)** for serialization + +### Memory Layout + +``` +AppError: 120 bytes (on x86_64) +├─ kind: 1 byte (enum discriminant) +├─ code: 2 bytes (enum discriminant) +├─ message: 24 bytes (String) +├─ edit_policy: 1 byte (enum discriminant) +├─ metadata: 24 bytes (Vec) +├─ source: 16 bytes (Option>) +├─ backtrace: 16 bytes (Option) +├─ retry_after: 16 bytes (Option) +└─ www_authenticate: 24 bytes (Option) +``` + +Field: 40-48 bytes depending on variant (unoptimized enum layout). + +### Benchmark Results + +Typical performance on modern x86_64 CPU: + +- **Error creation with metadata**: ~50-100ns +- **Context into_error conversion**: ~80-150ns +- **ProblemJson serialization**: ~300-500ns +- **Full HTTP response generation**: ~800-1200ns + +Regressions >10% from these baselines fail CI. + +## Design Patterns + +### 1. Builder Pattern + +`Context` and `AppError` fluent APIs: + +```rust +AppError::internal("db error") + .with_field(field::str("table", "users")) + .with_retry_after_duration(Duration::from_secs(30)) +``` + +### 2. Strategy Pattern + +Redaction policies encapsulate field masking strategies: + +```rust +enum RedactionPolicy { + None, + Redact, + Hash, + Last4, +} +``` + +### 3. Adapter Pattern + +Transport layers adapt `AppError` to framework-specific types: + +```rust +impl IntoResponse for AppError { + fn into_response(self) -> Response { + // Adapt to Axum's Response type + } +} +``` + +### 4. Type-State Pattern + +`Context` ensures errors are constructed correctly before conversion: + +```rust +Context::new(kind, message) // Incomplete state + .with_field(field) // Still building + .into_error() // Transition to complete state +``` + +### 5. Extension Trait Pattern + +`ResultExt` adds methods to standard `Result`: + +```rust +pub trait ResultExt { + fn with_context(self, f: F) -> Result + where + F: FnOnce(E) -> AppError; +} +``` + +### 6. Derive Macro Pattern + +Procedural macros generate boilerplate implementations: + +```rust +#[derive(Error)] // Generates Display, Error, From impls +``` + +## Compile-Time Guarantees + +### 1. No Unsafe Code + +Enforced by: +```toml +[lints.rust] +unsafe_code = "forbid" +``` + +### 2. Type Safety + +- Error kinds are enum variants (no string matching) +- Metadata fields are strongly typed (no `HashMap`) +- Source errors preserve type information via trait objects + +### 3. Send + Sync Bounds + +All errors implement `Send + Sync` for concurrent usage: + +```rust +impl Error for AppError + Send + Sync + 'static +``` + +### 4. No Panics in Library Code + +Enforced by code review and testing. Only `unreachable!()` after exhaustive matches. + +### 5. Feature Flag Isolation + +Feature combinations tested in CI via matrix: + +```yaml +features: [ + ["std"], + ["std", "axum"], + ["std", "actix"], + ["std", "turnkey"], + ["std", "axum", "sqlx", "tracing"], +] +``` + +### 6. MSRV Guarantees + +CI tests on MSRV (1.90) and stable to prevent accidental newer Rust usage. + +## Deployment Considerations + +### Feature Flag Selection + +**Minimal setup** (library use): +```toml +masterror = { version = "0.24", default-features = false } +``` + +**HTTP service**: +```toml +masterror = { version = "0.24", features = ["std", "axum", "tracing"] } +``` + +**Full-stack service**: +```toml +masterror = { version = "0.24", features = [ + "std", "axum", "sqlx", "reqwest", "redis", + "tracing", "metrics", "backtrace" +] } +``` + +**Rapid prototype**: +```toml +masterror = { version = "0.24", features = ["turnkey"] } +``` + +### Error Budget + +Typical error overhead per request: +- **CPU**: 100-200ns error creation + 500-1000ns serialization +- **Memory**: 120 bytes base + 40 bytes per metadata field +- **Allocations**: 1-3 allocations per error (depending on metadata) + +For high-throughput services (>100k req/s), consider: +- Reusing error instances via thread-local storage +- Limiting metadata fields to <5 per error +- Disabling backtrace capture in production + +### Observability Integration + +**Tracing**: +```rust +#[instrument(err)] +fn operation() -> Result { + // Errors automatically logged with span context +} +``` + +**Metrics**: +```rust +let counter = error_counter(err.kind, err.code); +counter.increment(1); +``` + +## Future Architecture Evolution + +### Planned Enhancements + +1. **Async context propagation**: Store metadata in tokio task-local storage +2. **OpenTelemetry native**: Directly export errors as OTel events +3. **Error aggregation**: Batch errors for distributed tracing +4. **Recovery strategies**: Optional retry/fallback builders + +### Stability Guarantees + +- **Core types**: Stable, semver-compatible +- **Transport adapters**: Semver-minor for new adapters +- **Derive macros**: Syntax is stable, expansion may improve +- **Turnkey module**: May evolve with breaking changes (opt-in) + +### Deprecation Policy + +- Deprecated features remain for 2 minor versions +- Migration guides provided in CHANGELOG +- Compiler warnings guide users to replacements diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d940e0a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,135 @@ + + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Project maintainers are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the project maintainers responsible for enforcement at +andrey.rozanov.vl@gmail.com. + +All complaints will be reviewed and investigated promptly and fairly. + +All project maintainers are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Project maintainers will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from project maintainers, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2d030d6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,519 @@ + + +# Contributing to masterror + +Thank you for considering contributing to masterror. This document outlines the development process, coding standards, and quality requirements. + +## Table of Contents + +1. [Getting Started](#getting-started) +2. [Development Environment](#development-environment) +3. [Coding Standards](#coding-standards) +4. [Testing Requirements](#testing-requirements) +5. [Pull Request Process](#pull-request-process) +6. [Commit Message Format](#commit-message-format) +7. [Code Review Guidelines](#code-review-guidelines) +8. [Performance Benchmarking](#performance-benchmarking) +9. [Documentation Standards](#documentation-standards) +10. [Issue Reporting](#issue-reporting) + +## Getting Started + +### Prerequisites + +- **Rust**: MSRV 1.90 (stable and nightly toolchains recommended) +- **Tools**: `cargo-audit`, `cargo-deny`, `cargo-tarpaulin` or `cargo-llvm-cov` (for coverage) +- **GitHub CLI**: `gh` (optional but recommended for workflow management) + +### Initial Setup + +```bash +git clone https://github.com/RAprogramm/masterror.git +cd masterror + +rustup toolchain install stable nightly +rustup component add clippy rustfmt --toolchain nightly + +cargo build --all-features +cargo test --all-features +``` + +### Workspace Structure + +``` +masterror/ +├── src/ # Core library +├── masterror-derive/ # Procedural macros +├── masterror-template/ # Template parser +├── tests/ # Integration tests +├── benches/ # Criterion benchmarks +├── examples/ # Usage examples +├── docs/ # Extended documentation +└── .github/ # CI/CD workflows +``` + +## Development Environment + +### Editor Configuration + +Configure your editor to: +- Use 4 spaces for indentation +- Maximum line length: 99 characters +- Trim trailing whitespace +- Insert final newline + +### Recommended Extensions + +**VS Code**: +- rust-analyzer +- Even Better TOML +- crates + +**IntelliJ IDEA**: +- Rust plugin + +## Coding Standards + +This project adheres to the [RustManifest](https://github.com/RAprogramm/RustManifest) guidelines, which define comprehensive standards for professional Rust development. + +### Style Guide + +We follow Rust standard formatting with project-specific overrides in `.rustfmt.toml` based on [RustManifest .rustfmt.toml reference](https://github.com/RAprogramm/RustManifest/blob/main/.rustfmt.toml): + +```bash +cargo +nightly fmt --all +``` + +**Key principles**: +- Line limit: 99 characters +- Imports: grouped (std, external, internal) +- No `::` operator except in `use` statements +- Avoid `unwrap()` and `expect()` in library code (tests are exempt) +- Minimize `clone()` usage +- Meaningful variable names; constants in `SCREAMING_SNAKE_CASE` + +### Documentation Requirements + +Every public item SHALL have: + +1. **Summary**: One-line description +2. **Details**: Behavior explanation +3. **Parameters**: Document all parameters +4. **Return value**: Document return semantics +5. **Errors**: Document error conditions +6. **Examples**: At least one doctest +7. **Safety**: Document unsafe code (if any) + +Example: + +```rust +/// Converts a domain error into an application error with telemetry. +/// +/// This function attaches structured metadata fields to the error context, +/// applies the specified redaction policy, and maps the error kind to the +/// appropriate transport-level representation. +/// +/// # Parameters +/// +/// - `source`: The source error to convert +/// - `metadata`: Structured telemetry fields to attach +/// +/// # Returns +/// +/// Returns an `AppError` with the source error, metadata, and appropriate +/// error kind classification. +/// +/// # Examples +/// +/// ``` +/// use masterror::{AppError, field}; +/// +/// let err = AppError::internal("db connection lost") +/// .with_field(field::str("table", "users")); +/// assert_eq!(err.metadata().len(), 1); +/// ``` +pub fn with_field(self, field: Field) -> Self { + // implementation +} +``` + +### Error Handling + +- All fallible operations return `Result` +- Use `masterror::AppError` for application-level errors +- Preserve error source chains +- Attach relevant context via metadata +- No panics except in tests and `unreachable!()` after exhaustive matches + +### Type System Usage + +- Prefer `&str` over `String` when ownership is not required +- Use `&[T]` instead of `&Vec` in function parameters +- Use `Vec::with_capacity()` when size is known +- Avoid unnecessary trait bounds on struct definitions + +## Testing Requirements + +### Coverage Target + +Minimum: **95%** | Target: **100%** + +### Test Categories + +#### 1. Unit Tests + +Located in `#[cfg(test)] mod tests` blocks within source files. + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn error_creation_attaches_metadata() { + let err = AppError::internal("test") + .with_field(field::str("key", "value")); + assert_eq!(err.metadata().len(), 1); + } + + #[test] + fn invalid_input_returns_bad_request() { + let result = validate_input(""); + assert!(matches!( + result.unwrap_err().kind, + AppErrorKind::BadRequest + )); + } +} +``` + +#### 2. Integration Tests + +Located in `tests/` directory. + +```rust +use masterror::prelude::*; + +#[test] +fn axum_integration_converts_app_error_to_response() { + let err = AppError::not_found("resource missing"); + let response = axum::response::IntoResponse::into_response(err); + assert_eq!(response.status(), StatusCode::NOT_FOUND); +} +``` + +#### 3. Doctests + +Every public API example SHALL compile and run: + +```rust +/// ``` +/// use masterror::AppError; +/// let err = AppError::unauthorized("token expired"); +/// assert_eq!(err.kind, masterror::AppErrorKind::Unauthorized); +/// ``` +``` + +### Test Principles + +1. **Deterministic**: No time, network, or environment dependencies +2. **Isolated**: Tests do not share mutable state +3. **Fast**: Unit tests complete in milliseconds +4. **Comprehensive**: Cover happy paths, error paths, edge cases + +### Error Path Testing + +Every error variant SHALL be tested: + +```rust +#[test] +fn constraint_violation_maps_to_conflict() { + let db_err = simulate_unique_violation(); + let app_err: AppError = db_err.into(); + assert!(matches!(app_err.kind, AppErrorKind::Conflict)); +} +``` + +### Running Tests + +```bash +cargo test --all-features +cargo test --no-default-features +cargo test --test integration_http +cargo test --doc + +MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test +``` + +## Pull Request Process + +### 1. Create Issue First + +Before implementing features or fixes: + +1. Search existing issues to avoid duplicates +2. Create issue describing problem/feature +3. Wait for maintainer acknowledgment (for large changes) + +### 2. Branch Naming + +Branch name SHALL be the issue number without prefixes: + +```bash +git checkout -b 123 +``` + +### 3. Implementation + +Make changes following coding standards. Commit frequently with descriptive messages. + +### 4. Pre-PR Checklist + +Before submitting, verify: + +```bash +cargo +nightly fmt --all --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-features +cargo bench --no-run --features benchmarks +cargo doc --no-deps --all-features +``` + +### 5. Cancel Previous Workflows + +Before pushing, cancel in-progress workflows to save CI resources: + +```bash +gh run list --branch $(git branch --show-current) --status in_progress \ + --json databaseId --jq '.[].databaseId' | xargs -I {} gh run cancel {} +``` + +### 6. Push and Create PR + +```bash +git push -u origin 123 +gh pr create --title "123" --body "$(cat <<'EOF' +## Summary + +Brief description of changes. + +## Changes + +- Added X +- Fixed Y +- Updated Z + +## Test Plan + +- [ ] Unit tests pass +- [ ] Integration tests pass +- [ ] Benchmarks show no regression +- [ ] Documentation updated + +Closes #123 +EOF +)" +``` + +### 7. PR Review + +- Respond to feedback promptly +- Push additional commits to address comments +- Request re-review after changes +- Do not force-push after initial review + +### 8. Merge + +Maintainers will merge after: +- All CI checks pass +- At least one approval +- No unresolved conversations + +## Commit Message Format + +``` +# : + +[optional body] + +[optional footer] +``` + +### Types + +- `feat`: New feature +- `fix`: Bug fix +- `perf`: Performance improvement +- `refactor`: Code restructuring without behavior change +- `test`: Adding or updating tests +- `docs`: Documentation changes +- `ci`: CI/CD changes +- `chore`: Maintenance tasks + +### Examples + +``` +#42 feat: add async handler support + +Implements async error handlers for Axum and Actix-web frameworks. +Handlers can now return async closures that resolve to AppError. + +#89 fix: correct gRPC status mapping for timeout errors + +Previously mapped to UNAVAILABLE, now correctly maps to DEADLINE_EXCEEDED +per gRPC specification. + +#120 perf: optimize metadata serialization path + +Reduces allocations in ProblemJson conversion by 40%. +Benchmark results show 200ns improvement on average. +``` + +## Code Review Guidelines + +### For Contributors + +- Keep PRs focused (one issue per PR) +- Respond to feedback within 48 hours +- Accept constructive criticism professionally +- Ask questions if feedback is unclear + +### For Reviewers + +Review for: +1. **Correctness**: Does it solve the problem? +2. **Testing**: Adequate test coverage? +3. **Performance**: Any obvious inefficiencies? +4. **Documentation**: Public APIs documented? +5. **Style**: Follows project conventions? +6. **Safety**: No unsafe code without justification? + +Use these labels: +- "looks good to me" (LGTM): Approve +- "request changes": Blocking issues +- "comment": Non-blocking suggestions + +## Performance Benchmarking + +### Running Benchmarks + +```bash +cargo bench --features benchmarks --bench error_paths +cargo bench --features benchmarks --bench error_paths -- --save-baseline main +``` + +### Adding Benchmarks + +Place criterion benchmarks in `benches/`: + +```rust +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use masterror::AppError; + +fn benchmark_error_creation(c: &mut Criterion) { + c.bench_function("create_error_with_metadata", |b| { + b.iter(|| { + black_box(AppError::internal("test") + .with_field(field::str("key", "value"))) + }); + }); +} + +criterion_group!(benches, benchmark_error_creation); +criterion_main!(benches); +``` + +### Regression Policy + +CI will fail if benchmarks show >10% performance regression. If regression is necessary: +1. Document rationale in PR description +2. Add `[perf-regression-justified]` label +3. Require maintainer approval + +## Documentation Standards + +### README Updates + +Update README.md when adding: +- New features requiring examples +- Breaking changes requiring migration +- New feature flags + +### CHANGELOG + +Add entries to CHANGELOG.md under "Unreleased" section: + +```markdown +## [Unreleased] + +### Added +- Support for async error handlers (#42) + +### Fixed +- Incorrect gRPC status mapping for timeouts (#89) + +### Changed +- BREAKING: Renamed `ErrorContext` to `Context` (#105) + +### Performance +- Reduced metadata serialization allocations by 40% (#120) +``` + +### Wiki and Guides + +Extended documentation goes in `docs/wiki/`: +- Step-by-step tutorials +- Migration guides +- Comparison with alternatives +- Troubleshooting recipes + +## Issue Reporting + +### Bug Reports + +Use the bug report template and include: +1. Minimal reproducible example +2. Expected vs actual behavior +3. Environment (Rust version, OS, features enabled) +4. Error messages and stack traces + +### Feature Requests + +Use the feature request template and include: +1. Use case description +2. Proposed API (if applicable) +3. Alternatives considered +4. Impact assessment + +### Questions + +For usage questions: +1. Check existing documentation and issues first +2. Use GitHub Discussions (if enabled) or issues +3. Provide context and code samples + +## Release Process + +(For maintainers) + +1. Update CHANGELOG.md with version number and date +2. Update version in all `Cargo.toml` files +3. Run full CI locally +4. Create git tag: `git tag -a v0.x.y -m "Release v0.x.y"` +5. Push tag: `git push origin v0.x.y` +6. GitHub Actions will publish to crates.io +7. Create GitHub release with CHANGELOG excerpt + +## Getting Help + +- **Documentation**: https://docs.rs/masterror +- **Issues**: https://github.com/RAprogramm/masterror/issues +- **Email**: andrey.rozanov.vl@gmail.com + +## License + +By contributing, you agree that your contributions will be dual-licensed under MIT OR Apache-2.0. diff --git a/README.md b/README.md index 46881af..4ac06c9 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,7 @@ assert_eq!(problem.grpc.expect("grpc").name, "UNAUTHENTICATED"); - Browse the [crate documentation on docs.rs](https://docs.rs/masterror) for API details, feature-specific guides and transport tables. - Check [`CHANGELOG.md`](CHANGELOG.md) for release highlights and migration notes. +- Review [RustManifest](https://github.com/RAprogramm/RustManifest) for the development standards and best practices this project follows. --- diff --git a/README.template.md b/README.template.md index e4881ca..44b149f 100644 --- a/README.template.md +++ b/README.template.md @@ -472,6 +472,7 @@ assert_eq!(problem.grpc.expect("grpc").name, "UNAUTHENTICATED"); - Browse the [crate documentation on docs.rs](https://docs.rs/masterror) for API details, feature-specific guides and transport tables. - Check [`CHANGELOG.md`](CHANGELOG.md) for release highlights and migration notes. +- Review [RustManifest](https://github.com/RAprogramm/RustManifest) for the development standards and best practices this project follows. --- diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 0000000..fc67723 --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,421 @@ + + +# Requirements Specification + +> This specification follows the [RustManifest](https://github.com/RAprogramm/RustManifest) development standards, which define professional guidelines for Rust project structure, code quality, testing, and documentation. + +## 1. Problem Statement + +### 1.1 Context + +Modern Rust services require consistent, observable error handling across multiple transport layers (HTTP, gRPC, WebAssembly) and integration boundaries (databases, HTTP clients, message queues). Existing error handling libraries present several challenges: + +1. **Fragmentation**: `thiserror` provides derives but lacks transport mappings; `anyhow` offers dynamic errors but loses type safety; combining both creates inconsistent error surfaces. + +2. **Transport overhead**: Mapping domain errors to HTTP status codes, gRPC status values, and RFC 7807 Problem Details requires repetitive glue code in every service. + +3. **Telemetry gaps**: Structured error context (request IDs, user IDs, operation metadata) is typically stored as strings in ad-hoc maps, losing type safety and making redaction policies difficult to enforce. + +4. **Integration burden**: Each third-party library error (sqlx, reqwest, redis) needs manual conversion logic, source chain preservation, and context attachment. + +5. **Framework coupling**: Error types often become tightly coupled to web frameworks (Actix, Axum), making code reuse across projects difficult. + +### 1.2 Target Users + +1. **Backend service developers** building microservices with multiple transport protocols +2. **Library authors** requiring framework-agnostic error types with rich context +3. **Platform teams** establishing error handling standards across service fleets +4. **WebAssembly developers** needing browser-compatible error reporting without heap allocation requirements + +### 1.3 Goals + +1. Provide a unified error taxonomy that maps cleanly to HTTP, gRPC, and RFC 7807 Problem Details +2. Offer typed, structured telemetry with field-level redaction controls +3. Enable framework-agnostic error handling with optional transport adapters +4. Deliver zero-cost abstractions with no unsafe code +5. Maintain comprehensive test coverage and documentation + +### 1.4 Non-Goals + +1. Replace all uses of `thiserror` or `anyhow` in existing codebases (compatibility is preferred) +2. Provide dynamic error type erasure by default (users can opt into this via features) +3. Offer automatic error recovery or retry logic (this is application-specific) +4. Support runtime error message localization (compile-time message selection is possible) + +## 2. Use Cases + +### 2.1 UC-001: HTTP Service Error Handling + +**Actor**: Backend service developer +**Precondition**: Service uses Axum or Actix-web +**Trigger**: Business logic returns domain error +**Main Flow**: +1. Domain function returns `Result` +2. `DomainError` implements `From for AppError` +3. Framework automatically converts `AppError` to HTTP response +4. Client receives RFC 7807 Problem Details with appropriate status code + +**Postcondition**: Error is logged with structured telemetry, client receives consistent error format + +**Alternative Flows**: +- A1: If error contains sensitive data, redaction policy masks fields before serialization +- A2: If retry hint is set, `Retry-After` header is included in response + +### 2.2 UC-002: gRPC Status Mapping + +**Actor**: gRPC service developer +**Precondition**: Service uses Tonic +**Trigger**: Business logic returns domain error +**Main Flow**: +1. Domain function returns `Result` +2. `DomainError` defines gRPC status code mapping via derive macro +3. Error is converted to `tonic::Status` with message and details +4. gRPC client receives status with structured metadata + +**Postcondition**: Error maintains consistent mapping between HTTP and gRPC transports + +### 2.3 UC-003: Database Error Integration + +**Actor**: Service using sqlx +**Precondition**: Database query fails +**Trigger**: sqlx returns `sqlx::Error` +**Main Flow**: +1. sqlx operation returns error +2. `From for AppError` converts error with telemetry +3. Constraint violations map to `AppErrorKind::Conflict` +4. Connection errors map to `AppErrorKind::Service` +5. Query-specific metadata (table name, operation) is attached + +**Postcondition**: Database errors are consistently classified and observable + +### 2.4 UC-004: Structured Telemetry Attachment + +**Actor**: Developer instrumenting error paths +**Precondition**: Operation has contextual metadata +**Trigger**: Error occurs during operation +**Main Flow**: +1. Developer creates error with metadata builders +2. Metadata includes typed fields (strings, integers, durations, IPs) +3. Fields have individual redaction policies (hash, last4, redact, none) +4. Error propagates through call stack preserving metadata +5. Telemetry backend receives structured fields + +**Postcondition**: Errors are observable without string concatenation or manual JSON building + +### 2.5 UC-005: Custom Domain Error Derivation + +**Actor**: Library author defining domain errors +**Precondition**: Domain has specific error taxonomy +**Trigger**: Need to integrate domain errors with masterror +**Main Flow**: +1. Define domain error enum with variants +2. Apply `#[derive(Error, Masterror)]` with per-variant mappings +3. Specify `AppCode`, `AppErrorKind`, telemetry, and redaction per variant +4. Framework automatically converts to HTTP/gRPC responses +5. Source errors and backtraces are preserved + +**Postcondition**: Domain errors integrate seamlessly without manual glue code + +## 3. Functional Requirements + +### 3.1 Core Error Types + +**FR-001**: System SHALL provide `AppError` type with kind, code, message, metadata, and source +**FR-002**: System SHALL provide `AppErrorKind` enum covering HTTP 4xx/5xx status classes +**FR-003**: System SHALL provide `AppCode` enum for fine-grained error classification +**FR-004**: System SHALL support error source chaining via `std::error::Error` +**FR-005**: System SHALL support backtrace capture when enabled + +### 3.2 Metadata System + +**FR-006**: System SHALL support typed metadata fields (string, i64, u64, f64, Duration, IpAddr, JSON) +**FR-007**: System SHALL provide field-level redaction policies (none, hash, last4, redact) +**FR-008**: System SHALL provide builders for common field types +**FR-009**: System SHALL allow metadata attachment at error creation time +**FR-010**: System SHALL preserve metadata through error conversions + +### 3.3 Derive Macros + +**FR-011**: System SHALL provide `#[derive(Error)]` compatible with thiserror syntax +**FR-012**: System SHALL provide `#[derive(Masterror)]` for full integration +**FR-013**: System SHALL support per-variant mappings in enum errors +**FR-014**: System SHALL generate `From` implementations for source errors +**FR-015**: System SHALL support telemetry attachment via derive attributes + +### 3.4 Transport Mappings + +**FR-016**: System SHALL map `AppErrorKind` to HTTP status codes +**FR-017**: System SHALL map `AppErrorKind` to gRPC status codes +**FR-018**: System SHALL generate RFC 7807 Problem Details JSON +**FR-019**: System SHALL support custom Problem type URIs +**FR-020**: System SHALL include retry hints in HTTP headers when specified + +### 3.5 Integrations + +**FR-021**: System SHALL provide `From` implementations for sqlx errors +**FR-022**: System SHALL provide `From` implementations for reqwest errors +**FR-023**: System SHALL provide `From` implementations for redis errors +**FR-024**: System SHALL provide `From` implementations for validator errors +**FR-025**: System SHALL provide `From` implementations for config errors +**FR-026**: System SHALL provide `From` implementations for tokio errors +**FR-027**: System SHALL provide `From` implementations for teloxide errors +**FR-028**: System SHALL provide `From` implementations for multipart errors + +### 3.6 Feature Flags + +**FR-029**: System SHALL compile with no default features enabled +**FR-030**: System SHALL provide opt-in features for each integration +**FR-031**: System SHALL provide opt-in features for each transport +**FR-032**: System SHALL provide opt-in features for telemetry backends +**FR-033**: System SHALL provide turnkey feature with opinionated defaults + +### 3.7 Documentation + +**FR-034**: System SHALL document all public types with doc comments +**FR-035**: System SHALL provide doctests for all public APIs +**FR-036**: System SHALL maintain comprehensive README with examples +**FR-037**: System SHALL publish documentation on docs.rs +**FR-038**: System SHALL maintain CHANGELOG with migration notes + +## 4. Non-Functional Requirements + +### 4.1 Performance + +**NFR-001**: Error creation SHALL NOT allocate on the happy path +**NFR-002**: Metadata attachment SHALL use `Vec::with_capacity` for known sizes +**NFR-003**: Error conversions SHALL complete in <100ns for common paths (measured via criterion) +**NFR-004**: RFC 7807 serialization SHALL complete in <500ns for typical errors +**NFR-005**: Performance regressions >10% SHALL fail CI benchmarks + +### 4.2 Memory Safety + +**NFR-006**: System SHALL contain zero unsafe code +**NFR-007**: System SHALL pass Miri tests under stacked borrows +**NFR-008**: System SHALL prevent memory leaks (validated via leak sanitizer) +**NFR-009**: System SHALL use only safe abstractions from std and alloc + +### 4.3 Code Quality + +**NFR-010**: System SHALL maintain >95% code coverage (target: 100%) +**NFR-011**: System SHALL pass clippy with `-D warnings` +**NFR-012**: System SHALL format code with `cargo +nightly fmt` +**NFR-013**: System SHALL pass cargo-deny license and advisory checks +**NFR-014**: System SHALL pass cargo-audit security vulnerability checks + +### 4.4 Compatibility + +**NFR-015**: System SHALL support MSRV 1.90 +**NFR-016**: System SHALL support Rust edition 2024 +**NFR-017**: System SHALL support no_std environments (with alloc) +**NFR-018**: System SHALL support WASM targets (with frontend feature) +**NFR-019**: System SHALL maintain semver compatibility guarantees + +### 4.5 Build Characteristics + +**NFR-020**: Core crate SHALL compile in <30s on release profile +**NFR-021**: Full feature set SHALL compile in <60s on release profile +**NFR-022**: Default feature set SHALL have <10 direct dependencies +**NFR-023**: Release builds SHALL enable LTO and single codegen unit +**NFR-024**: Documentation builds SHALL complete without warnings + +### 4.6 Testing + +**NFR-025**: System SHALL include unit tests for all functions +**NFR-026**: System SHALL include integration tests for all transport adapters +**NFR-027**: System SHALL include doctests for all public APIs +**NFR-028**: System SHALL test error paths, not just happy paths +**NFR-029**: System SHALL test concurrent error handling (no data races) +**NFR-030**: System SHALL test resource cleanup (no file/socket leaks) + +### 4.7 CI/CD + +**NFR-031**: CI SHALL run on every PR and main branch push +**NFR-032**: CI SHALL test MSRV and stable Rust in matrix +**NFR-033**: CI SHALL run fmt, clippy, tests, coverage, benchmarks, audit +**NFR-034**: CI SHALL fail on any warnings or test failures +**NFR-035**: CI SHALL upload coverage reports to Codecov +**NFR-036**: CI SHALL cancel redundant runs via concurrency groups + +### 4.8 Documentation + +**NFR-037**: Every public type SHALL have doc comments with examples +**NFR-038**: Every public function SHALL document parameters and return values +**NFR-039**: Complex algorithms SHALL have explanatory comments +**NFR-040**: Migration guides SHALL document breaking changes +**NFR-041**: Architecture decisions SHALL be documented in ADRs + +## 5. Architecture Decision Records + +### 5.1 ADR-001: Framework-Agnostic Core + +**Status**: Accepted +**Context**: Need to support multiple web frameworks and non-HTTP use cases +**Decision**: Core types in separate crate with optional transport adapters via features +**Consequences**: +- Positive: Reusable across projects, no framework lock-in +- Positive: Smaller compile times for users not needing all transports +- Negative: Requires feature flag management +- Negative: Transport impls must be maintained separately + +### 5.2 ADR-002: Typed Metadata Over String Maps + +**Status**: Accepted +**Context**: Need structured telemetry without losing type safety +**Decision**: Provide typed field builders (i64, u64, Duration, IpAddr) with per-field redaction +**Consequences**: +- Positive: Type-safe context attachment +- Positive: Granular redaction control +- Positive: Better telemetry backend integration +- Negative: More complex API than simple HashMap +- Negative: Serialization logic must handle multiple types + +### 5.3 ADR-003: Native Derive Macros + +**Status**: Accepted +**Context**: Need to integrate domain errors without manual glue code +**Decision**: Implement derive macros compatible with thiserror syntax plus masterror extensions +**Consequences**: +- Positive: Seamless migration from thiserror +- Positive: Reduced boilerplate +- Positive: Compile-time validation of mappings +- Negative: Proc macro complexity +- Negative: Error messages from macros can be cryptic + +### 5.4 ADR-004: Conservative HTTP/gRPC Mappings + +**Status**: Accepted +**Context**: Need consistent error classification across transports +**Decision**: Provide default mappings with override capability +**Consequences**: +- Positive: Consistent out-of-box behavior +- Positive: Reduces mapping errors +- Positive: Teams can customize per domain +- Negative: Defaults may not fit all use cases +- Negative: Requires documentation of mapping rationale + +### 5.5 ADR-005: No Unsafe Code + +**Status**: Accepted +**Context**: Target WASM and security-critical services +**Decision**: Implement all functionality with safe Rust abstractions +**Consequences**: +- Positive: Memory safety guarantees +- Positive: WASM compatibility +- Positive: Easier to audit +- Negative: May lose some performance optimizations +- Negative: Cannot use certain low-level tricks + +### 5.6 ADR-006: Opt-In Feature Flags + +**Status**: Accepted +**Context**: Avoid forcing dependencies on users who don't need them +**Decision**: All integrations and transports behind opt-in features, no defaults +**Consequences**: +- Positive: Minimal dependency footprint +- Positive: Faster compile times for minimal use cases +- Positive: Users only pay for what they use +- Negative: Users must explicitly enable features +- Negative: Documentation must explain feature combinations + +### 5.7 ADR-007: Turnkey Module for Rapid Adoption + +**Status**: Accepted +**Context**: Teams want opinionated defaults for quick onboarding +**Decision**: Provide optional turnkey module with pre-built catalog and helpers +**Consequences**: +- Positive: Faster time to value +- Positive: Consistent baseline across teams +- Positive: Can still customize after adoption +- Negative: Adds maintenance burden +- Negative: May not fit all domain models + +### 5.8 ADR-008: MSRV Policy + +**Status**: Accepted +**Context**: Balance modern Rust features with ecosystem compatibility +**Decision**: MSRV 1.90 with edition 2024 support +**Consequences**: +- Positive: Access to latest stable features +- Positive: Future-proof for edition migration +- Negative: Excludes older Rust installations +- Mitigation: MSRV bumps are semver-minor per Cargo policy + +## 6. Design Tradeoffs + +### 6.1 Type Safety vs Ergonomics + +**Choice**: Typed metadata fields over `HashMap` +**Rationale**: Compile-time type checking prevents runtime errors; redaction policies require field types +**Alternative**: Generic string maps (used by many logging libraries) +**Impact**: Slightly more verbose API, but safer and more observable + +### 6.2 Feature Flags vs Monolithic Crate + +**Choice**: Extensive feature flags with no defaults +**Rationale**: Minimize compile time and dependency footprint for minimal use cases +**Alternative**: All-in-one crate with all features enabled +**Impact**: Users must read docs to enable features, but gain significant compile time improvements + +### 6.3 thiserror Compatibility vs Novel Syntax + +**Choice**: Derive macros compatible with thiserror syntax +**Rationale**: Lower migration barrier, familiar to Rust developers +**Alternative**: Completely novel derive syntax +**Impact**: Constrained by thiserror design choices, but easier adoption + +### 6.4 Conservative Defaults vs Domain Flexibility + +**Choice**: Opinionated HTTP/gRPC mappings with override capability +**Rationale**: Consistency out of box, but allow customization +**Alternative**: No default mappings, require explicit configuration +**Impact**: Works immediately for 80% of use cases, customizable for edge cases + +### 6.5 Zero Unsafe vs Performance + +**Choice**: No unsafe code, even if it costs some performance +**Rationale**: Memory safety and WASM compatibility trump marginal perf gains +**Alternative**: Targeted unsafe for hot paths +**Impact**: May be slightly slower than hand-optimized alternatives, but safer + +## 7. Future Considerations + +### 7.1 Potential Enhancements + +1. **Async context propagation**: Integrate with tokio tracing spans +2. **Error recovery strategies**: Optional retry/fallback builders +3. **Metric integration**: Automatic error counters per kind/code +4. **OpenTelemetry**: Native span/event export +5. **Custom transports**: GraphQL, Protobuf, MessagePack adapters + +### 7.2 Known Limitations + +1. **Static messages**: Error messages are not localized at runtime +2. **No error recovery**: Library does not provide retry/circuit breaker logic +3. **Sync-first design**: Async-specific patterns (like tokio task cancellation) require manual integration +4. **JSON-only Problem Details**: XML RFC 7807 not currently supported + +### 7.3 Maintenance Commitments + +1. **MSRV updates**: Review quarterly, bump only for compelling features +2. **Dependency updates**: Monitor via Dependabot, apply security patches within 48 hours +3. **Breaking changes**: Follow semver strictly, provide migration guides +4. **Feature requests**: Prioritize framework-agnostic features over framework-specific + +## 8. Acceptance Criteria + +A release is considered ready when: + +1. All functional requirements (FR-001 through FR-038) are implemented +2. All non-functional requirements (NFR-001 through NFR-041) are verified +3. Code coverage is >95% (target: 100%) +4. CI passes on MSRV and stable Rust +5. Documentation builds without warnings +6. Benchmarks show no regressions >10% +7. Manual testing validates all transport adapters +8. CHANGELOG documents all user-facing changes +9. Migration guide exists for breaking changes +10. Security audit passes (cargo-audit, cargo-deny) diff --git a/SECURITY.md b/SECURITY.md index bc99130..1b57b7a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,20 +8,177 @@ SPDX-License-Identifier: MIT ## Supported Versions -Use this section to tell people about which versions of your project are -currently being supported with security updates. +We actively support the following versions with security updates: -| Version | Supported | -| ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | +| Version | Supported | MSRV | Status | +| ------- | ------------------ | ----- | ----------- | +| 0.24.x | :white_check_mark: | 1.90 | Active | +| 0.23.x | :x: | 1.90 | Deprecated | +| < 0.23 | :x: | 1.70+ | Deprecated | + +Security patches are released as patch versions (e.g., 0.24.19 → 0.24.20) for the actively supported version line. ## Reporting a Vulnerability -Use this section to tell people how to report a vulnerability. +We take security vulnerabilities seriously. If you discover a security issue in masterror, please report it responsibly. + +### Reporting Process + +1. **Do NOT** open a public GitHub issue for security vulnerabilities +2. **Email** security reports to: **andrey.rozanov.vl@gmail.com** +3. **Include** the following information: + - Description of the vulnerability + - Steps to reproduce or proof-of-concept + - Affected versions (if known) + - Potential impact assessment + - Suggested fix (if available) + +### Response Timeline + +- **Acknowledgment**: Within 48 hours of report +- **Initial assessment**: Within 5 business days +- **Fix development**: Varies by severity (critical: <7 days, high: <14 days) +- **Coordinated disclosure**: After patch is released and users have time to update + +### Security Update Process + +1. Vulnerability confirmed and assessed +2. Fix developed in private repository +3. Patch released as new version +4. Security advisory published on GitHub +5. Disclosure made after users have time to update (typically 7-14 days) + +## Security Best Practices + +When using masterror in production: + +### 1. Dependency Management + +- **Use latest stable version**: Security fixes are not backported to old versions +- **Enable Dependabot**: Automatically detect vulnerable dependencies +- **Run cargo-audit**: Regularly check for known vulnerabilities + +```bash +cargo install cargo-audit +cargo audit +``` + +### 2. Error Message Redaction + +- **Never expose sensitive data** in error messages without redaction +- **Use redaction policies** for PII (Personally Identifiable Information) + +```rust +use masterror::{AppError, field, RedactionPolicy}; + +let err = AppError::internal("auth failed") + .with_field(field::str_redacted( + "user_email", + email, + RedactionPolicy::Hash + )); +``` + +### 3. Production Configuration + +- **Disable backtraces** in production (avoids leaking internal paths) +- **Limit metadata fields** (avoid exposing internal state) +- **Use message redaction** for user-facing errors + +```toml +[dependencies] +masterror = { version = "0.24", features = ["std", "axum"], default-features = false } +``` + +### 4. Source Error Handling + +- **Avoid exposing third-party error details** to end users +- **Log detailed errors** internally, return sanitized errors to clients + +```rust +match db.query().await { + Ok(result) => Ok(result), + Err(e) => { + // Log full error internally + tracing::error!("Database error: {:?}", e); + + // Return sanitized error to client + Err(AppError::service("database unavailable")) + } +} +``` + +### 5. Supply Chain Security + +- **Verify crate signatures**: Use cargo-vet for supply chain verification +- **Review dependencies**: Audit transitive dependencies periodically +- **Pin versions**: Use exact versions in Cargo.lock for reproducible builds + +## Known Security Considerations + +### 1. Error Source Chains + +Error source chains may contain sensitive information from third-party libraries. Always review what is exposed to end users. + +### 2. Metadata Serialization + +Metadata fields are serialized in responses. Ensure redaction policies are correctly applied before exposing errors via HTTP/gRPC. + +### 3. Backtrace Leakage + +Backtraces can reveal internal file paths and stack frames. Disable the `backtrace` feature in production or implement custom filtering. + +### 4. Timing Attacks + +Error handling paths should not reveal timing information that could be exploited (e.g., database existence checks via different error latencies). This is application-specific and not handled by masterror. + +## Security Audits + +- **Internal audit**: Continuous review by maintainers +- **cargo-audit**: Automated dependency vulnerability scanning in CI +- **cargo-deny**: License and advisory checking in CI +- **Community review**: Open source code reviewed by users + +## Security-Related Features + +### No Unsafe Code + +masterror contains **zero unsafe code**, eliminating entire classes of memory safety vulnerabilities: + +```toml +[lints.rust] +unsafe_code = "forbid" +``` + +### Send + Sync Guarantees + +All error types implement `Send + Sync`, ensuring safe concurrent usage without data races. + +### Type Safety + +- Strongly typed error kinds (no string matching) +- Compile-time validation of transport mappings +- No type confusion between error categories + +## Vulnerability Disclosure History + +No security vulnerabilities have been reported to date. + +Future disclosures will be listed here with: +- CVE identifier (if applicable) +- Affected versions +- Severity rating +- Mitigation steps +- Fixed in version + +## Security Contact + +**Primary contact**: andrey.rozanov.vl@gmail.com + +**PGP key**: Available on request for encrypted communication + +## Additional Resources -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. +- [Rust Security Guidelines](https://anssi-fr.github.io/rust-guide/) +- [OWASP Error Handling Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Error_Handling_Cheat_Sheet.html) +- [Cargo Security Advisories](https://rustsec.org/)