gix-error 0.5.0

A crate of the gitoxide project to provide common errors and error-handling utilities
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
//! Common error types and utilities for error handling.
//!
//! # Classification and recovery
//!
//! Error classification matters because it lets a generic caller **choose a recovery approach**,
//! supporting resilient software.
//! Classes describe different remedies, not just different wording:
//!
//! | [`Class`] | Recovery approach |
//! | --- | --- |
//! | [`Cancelled`](Class::Cancelled) | Stop; preserve the caller's intent instead of automatically retrying. |
//! | [`Corruption`](Class::Corruption) | Repair, replace, or re-fetch malformed data. |
//! | [`ResourceExhaustion`](Class::ResourceExhaustion) | Reduce usage, adjust a limit, or free capacity. |
//! | [`Validation`](Class::Validation) | Correct the caller's input. |
//! | [`Unsupported`](Class::Unsupported) | Switch implementation, capability, format, protocol, or strategy. |
//! | [`Unauthenticated`](Class::Unauthenticated) | Obtain or refresh credentials, distinct from authenticated-but-forbidden. |
//! | [`PermissionDenied`](Class::PermissionDenied) | Obtain authorization or change permissions. |
//! | [`Conflict`](Class::Conflict) | Refresh or reconcile state before retrying against the new state. |
//! | [`NotFound`](Class::NotFound) | Create the resource, use a fallback, or accept absence. |
//! | [`Retryable`](Class::Retryable) | Consider a bounded retry, possibly after waiting. |
//!
//! The table follows [`Class`]'s suggested recovery precedence, highest first. [`Error::dominant_class()`]
//! and [`Exn::dominant_class()`] return the highest-precedence class present, or `None` for unclassified errors.
//! For borrowed errors, use [`classify()`] followed by [`dominant_class()`](types::Classifications::dominant_class).
//! This uses the smallest [`Class`] under its ordering, rather than the first classification in traversal order.
//!
//! **A class guides recovery; it does not establish that recovery is safe.** An operation whose outcome is
//! unknown may already have performed side effects, so retrying can duplicate them even after a transient failure.
//! Inspect concrete recovery errors and partial outcomes when necessary. Multiple causes can suggest different
//! remedies; a matching class does not make sibling failures ignorable. Both retry policies reject explicit
//! cancellation anywhere in the inspected causes; [`Error::is_retryable()`] only reports whether a retryable cause exists.
//!
//! Leave errors unclassified when the remedy is unknown. An external program's failed exit status alone does not
//! establish a class. Preserve genuine callee errors rather than assigning their context an unsupported guess.
//! Classify intrinsic conditions at their definition; use [`tag()`] for context-dependent classifications.
//!
//! # Usage
//!
//! Use [`Result`] and [`Error`] for public APIs with erased or message-based errors in plumbing crates and in `gix`.
//! Public traits, callbacks, iterator items, associated errors, and re-exported APIs follow the same rule.
//! It's Ok to preserve concrete error types already exposed by public signatures, including
//! [`ExnResult<T, Specific>`](ExnResult) and [`Exn<Specific>`](Exn), if this helps readability and consumers.
//! Native `std::io::Result`, standalone concrete-error results, and generic error adapters can retain their types.
//!
//! Private and `pub(crate)` implementations also use [`Result`] for erased or message-based errors.
//! Retain concrete errors when callers benefit from typed recovery or payload access. Use [`ExnResult<T, E>`](ExnResult)
//! or [`ExnMessageResult<T>`](ExnMessageResult) when callers need a specific exception type or manipulate exception trees.
//!
//! The default helpers return [`Error`] and [`Result`]; their `_typed` variants retain an [`Exn<E>`](Exn):
//!
//! | Default helper | Typed exception helper |
//! |---------------|------------------------|
//! | [`ErrorExt::raise()`] | [`ErrorExt::raise_typed()`] |
//! | [`ErrorExt::and_raise()`] | [`ErrorExt::and_raise_typed()`] |
//! | [`ResultExt::or_raise()`] | [`ResultExt::or_raise_typed()`] |
//! | [`OptionExt::ok_or_raise()`] | [`OptionExt::ok_or_raise_typed()`] |
//!
//! Use [`ResultExt::or_error()`] to convert native errors or exceptions to [`Result`] without adding context.
//! Existing [`Error`] values pass through unchanged. Conversions preserve concrete recovery errors, causes,
//! metadata, and caller locations; `?`, `.into()`, and [`Exn::into_error()`] also convert exceptions to [`Error`].
//! Use [`Error::into_exn()`] to recover an exception tree for internal processing, including rearranging child frames.
//!
//! # Caller locations
//!
//! The `error-print-location` feature enables caller locations in diagnostics. Paths with conventional
//! `src`, `tests`, `examples`, or `benches` directories are shortened to the containing package directory
//! and source path; Cargo registry package versions are omitted. For example,
//! `/home/user/.cargo/registry/src/index.crates.io-hash/gix-url-0.39.0/src/parse.rs:349`
//! is displayed as `gix-url/src/parse.rs:349`. Package-relative source paths are retained;
//! paths whose package root cannot be inferred from these directory conventions fall back to the filename.
//!
//! This only changes formatting: inspected locations retain the compiler-provided file, line, and column.
//! A location contains no Cargo package metadata, so a directory that differs from the package name cannot
//! be renamed automatically. Build owners can use rustc's `--remap-path-prefix` to control captured paths
//! for arbitrary source layouts. Alternate formatting continues to omit locations.
//!
//! # Standard Error Types
//!
//! Use these types for diagnostic context when recovery does not depend on a specific condition or structured payload.
//! Otherwise retain a concrete [`Error`](std::error::Error)-implementing type, and use it with
//! [`ResultExt::or_raise(<StandardErrorType>)`](ResultExt::or_raise) or
//! [`OptionExt::ok_or_raise(<StandardErrorType>)`](OptionExt::ok_or_raise), or sibling methods.
//!
//! All these types implement [`Error`](std::error::Error).
//!
//! ## [`Message`] and [`ClassificationMarker`]
//!
//! [`Message`] combines a diagnostic message, an optional [`Class`], and named scalar values. Use it
//! for diagnostic context, or instead of a chain of type-bearing errors when those layers only describe a single
//! failure. Keep concrete errors when callers need to match a particular condition, even without a payload.
//! [`not_found()`], [`validation()`], [`corruption()`], [`retryable()`], [`resource_exhaustion()`],
//! [`allocation_limit()`], [`allocation_failure()`], [`cancelled()`], [`permission_denied()`],
//! [`unauthenticated()`], [`conflict()`], and [`unsupported()`] construct classified messages.
//! [`message()`] and [`Message::new()`] start without a class or values. [`Message::with_class()`] and
//! [`Message::with()`] add them to the same diagnostic. Use [`message!`] for formatting, equivalent to
//! [`Message::new(format!("…"))`](Message::new) or `format!("…").into()`.
//! [`Message::corrupted()`] and [`Message::validation()`] are shortcuts for [`Class::Corruption`] and [`Class::Validation`],
//! respectively: `message!("invalid record at {offset}").corrupted()` or `message!("invalid count: {count}").validation()`.
//! The other classes have matching builders: [`Message::not_found()`], [`Message::retryable()`],
//! [`Message::resource_exhaustion()`], [`Message::allocation_limit()`], [`Message::allocation_failure()`],
//! [`Message::cancelled()`], [`Message::permission_denied()`], [`Message::unauthenticated()`],
//! [`Message::conflict()`], and [`Message::unsupported()`].
//! [`Message::corrupted_error()`], [`Message::validation_error()`], [`Message::not_found_error()`], [`Message::retryable_error()`],
//! [`Message::resource_exhaustion_error()`], [`Message::allocation_limit_error()`], [`Message::allocation_failure_error()`],
//! [`Message::cancelled_error()`], [`Message::permission_denied_error()`], [`Message::unauthenticated_error()`],
//! [`Message::conflict_error()`], and [`Message::unsupported_error()`]
//! combine these builders with [`ErrorExt::raise()`] when an [`Error`] is needed directly.
//! Prefer [`corruption()`] or [`validation()`] for static messages when more concise.
//!
//! Classification does not determine which diagnostic values can be attached. For example,
//! `corruption("Malformed reference").with_input(bytes)` preserves offending bytes in the same
//! error that describes their corruption. No extra validation error is needed just to store input.
//! Use explicit classified constructors or builders: converting a string to [`Message`] does not infer a class
//! from the function's return type.
//!
//! | Type | Diagnostic | Classification | Purpose |
//! |------|------------|----------------|---------|
//! | [`Message`] | Visible message and optional values | Optional | Describe a failure without a custom error type |
//! | [`ClassificationMarker`] | Transparent, no diagnostic of its own | Required | Classify an existing error while preserving its concrete type |
//!
//! ```
//! use gix_error::{ErrorExt, Message, MetadataValue};
//!
//! let error = gix_error::not_found("Reference does not exist")
//!     .with("path", std::path::Path::new("HEAD"))
//!     .raise();
//! assert!(error.is_not_found());
//! assert!(error.probable_cause().is::<Message>());
//! assert_eq!(error.metadata().next().expect("lookup details")["path"], MetadataValue::Path("HEAD".into()));
//! ```
//!
//! Callers should add context using information they already possess and document its keys on the function
//! that returns it. Preserve real callee errors, especially concrete recovery signals and complex results
//! discovered by the callee, such as partial outcomes:
//!
//! ```
//! use gix_error::{message, ResultExt, Message, MetadataValue};
//!
//! let error = Err::<(), _>(std::io::Error::from(std::io::ErrorKind::NotFound))
//!     .or_raise(|| message("Could not read reference").with("path", std::path::Path::new("HEAD")))
//!     .expect_err("the lookup failed");
//! assert!(error.is_not_found());
//! assert!(error.probable_cause().is::<std::io::Error>());
//! let context = error.error().downcast_ref::<Message>().expect("message context");
//! assert_eq!(context.class, None, "the callee, not the context, supplies the classification");
//! let values = error.metadata().next().expect("lookup context");
//! assert_eq!(values["path"], MetadataValue::Path("HEAD".into()));
//! ```
//!
//! [`Exn::metadata()`] and [`Error::metadata()`] yield each message's non-empty [`Metadata`] dictionary in error traversal order.
//! Each dictionary maps names to [`MetadataValue`]s. Keys are local to their context. Use [`Exn::metadata_merged()`] or
//! [`Error::metadata_merged()`] to obtain one owned dictionary, with more specific causes overriding enclosing contexts.
//! Merging independent causes discards their origin; later-visited values win. [`Metadata`] documents common schemas
//! for input validation and external program runtime failures.
//! These conventions do not require collecting additional information or imply a classification.
//! To identify a specific failure without inspecting its values, see
//! [matching a specific failure](#matching-a-specific-failure).
//!
//! # [`Exn<ErrorType>`](Exn) and [`Exn`]
//!
//! The [`Exn`] type does not implement [`Error`](std::error::Error) itself, but is able to store causing errors
//! via [`ResultExt::or_raise_typed()`] (and sibling methods) as well as location information of the creation site.
//!
//! Use exceptions in helpers whose callers need a typed context or tree operations such as [`Exn::chain_all()`].
//! [`Exn::erased`] and [`ExnResult<T>`](ExnResult) let these helpers combine different exception types.
//! Other helpers use [`Result`], which retains causes and locations without conversions at each caller.
//! Preserve concrete public exception signatures.
//!
//! Propagate existing [`Error`] values directly with `?` when the callee already provides enough context.
//! Use `.or_raise(|| message("context information"))` or its siblings when added context helps diagnose
//! the failure, explains the operation's purpose, or identifies user-controlled input such as configuration values.
//! Keep such context even when failures are rare, then errors serve as in-code explanation and intent.
//!
//! # Callback results
//!
//! Public and private callbacks with erased or message-based errors use [`Result<T>`](Result); concrete callback errors
//! retain their types. Add context with
//! [`ResultExt::or_raise()`] when propagating a callback failure:
//! ```
//! use gix_error::{message, Result, ResultExt};
//!
//! fn parse_count(input: &str) -> Result<u64> {
//!     input.parse::<u64>().or_raise(|| message("could not parse count"))
//! }
//!
//! pub fn process(callback: impl FnOnce() -> Result<u64>) -> Result<u64> {
//!     callback().or_raise(|| message("callback failed"))
//! }
//!
//! assert_eq!(process(|| parse_count("42"))?, 42);
//! # Ok::<(), gix_error::Error>(())
//! ```
//!
//! Private callbacks that need exception-tree operations may use [`ExnResult`] or [`ExnMessageResult`].
//! Use [`ResultExt::or_erased()`] only when such a callback requires an erased exception.
//!
//! # [`Error`] — `Exn` with `std::error::Error`
//!
//! Since [`Exn`] does not implement [`std::error::Error`], it cannot be used where that trait is required
//! (e.g. `std::io::Error::other()`, or as a `#[source]` in another error type).
//! The [`Error`] type bridges this gap: it implements [`std::error::Error`] and converts from any
//! [`Exn<E>`](Exn) via [`From`], preserving the full error tree and location information.
//!
//! ```rust,ignore
//! // Convert an Exn to something usable as std::error::Error:
//! let exn: Exn<Message> = message("something failed").raise_typed();
//! let err: gix_error::Error = exn.into();
//! let err: gix_error::Error = exn.into_error();
//!
//! // Useful where std::error::Error is required:
//! std::io::Error::other(exn.into_error())
//! ```
//!
//! It can also be created directly from any `std::error::Error` via [`Error::from_error()`], which preserves
//! native formatting for tree-backed errors. For native-error results, prefer [`ResultExt::or_error()`]
//! to capture the caller location and use exception formatting.
//! When a constructor is needed, invoke it in a closure, e.g. `.map_err(|err| Error::from_boxed(err))`.
//! Passing the constructor directly to an adapter captures a `FnOnce` shim's location instead of the call site.
//!
//! # Tests with [`TestResult`]
//!
//! Return [`TestResult`] from `#[test]` functions to propagate ordinary errors, [`Exn<E>`](Exn), and [`Error`]
//! directly with `?`. It defaults to `Result<(), TestError>`; helpers returning a value can use `TestResult<T>`.
//! Accepted errors must convert into `Box<dyn std::error::Error + Send + Sync + 'static>`.
//!
//! When a test returns an error, Rust's test harness prints [`TestError`]'s [`Debug`](std::fmt::Debug) output,
//! including the complete diagnostic tree or chain. Captured caller locations are printed when the
//! `error-print-location` feature is enabled; alternate formatting omits them.
//!
//! ```rust,test_harness
//! use gix_error::{message, ResultExt, TestResult};
//!
//! #[test]
//! fn parses_count() -> TestResult {
//!     let expected: usize = "42".parse()?;
//!     let actual = "42".parse::<usize>().or_raise(|| message("could not parse count"))?;
//!     assert_eq!(actual, expected, "context preserves the parsed count");
//!     Ok(())
//! }
//! ```
//!
//! # Migrating from `thiserror`
//!
//! This section describes the mechanical translation from `thiserror` error enums to `gix-error`.
//! In `Cargo.toml`, replace `thiserror = "<version>"` with `gix-error = { version = "^0.1.0", path = "../gix-error" }`.
//!
//! ## Choosing the replacement type
//!
//! Use [`Result`] for diagnostic messages, including validation failures without callee errors, in public and private code.
//! [`Message`] carries an optional class and named scalar values; [`Error`] retains the diagnostic context and causes.
//! Keep a concrete error type in [`ExnResult`] when recovery requires a specific condition or structured payload.
//! Public trait associated types also use [`Error`] for message-based errors, including bare [`Message`] errors.
//! Preserve concrete public exception signatures. Internal results retain [`ExnMessageResult`] or [`ExnResult`] when
//! callers need a specific exception type or exception-tree operations.
//! Define at most one operation-specific error type, normally a public, `#[non_exhaustive]` enum named `Error`.
//! Related methods should share it. Broad [`Class`] values categorize errors; variants
//! define specific recovery decisions. Preserve genuine callee errors as causes instead of formatting them into text.
//!
//! Use the chosen type directly in signatures, importing it under its canonical name where helpful.
//! Crate-specific and operation-specific forwarding aliases or renamed error exports are unnecessary.
//! Facades may re-export the canonical types, as `gix` does with `Error`, `Exn`, `Result`, `ExnResult`, and `ExnMessageResult`.
//! Always import the result aliases directly and use their bare names in signatures.
//!
//! ## Translating variants
//!
//! Translate variants to messages only when they provide diagnostics without a specific recovery contract.
//! Use [`.raise()`](ErrorExt::raise) to wrap standalone errors into an [`Error`], and
//! [`ResultExt::or_raise()`] to preserve callee errors with additional context.
//! For early returns, use [`bail!`] with a concrete error, message, or format arguments, such as
//! `bail!(Error::SomethingFailed)`, `bail!(message("something went wrong"))`, or `bail!("invalid input: {input}")`.
//! Prefer to import `bail` with `use gix_error::bail;` (or `use gix::error::bail;` in applications) and
//! invoke it as `bail!(...)` instead of using a qualified path.
//! String shorthand is unclassified unless a builder supplies a class. In plumbing APIs, classify known input or data failures explicitly, e.g.
//! `bail!(validation("invalid input"))` or `bail!("invalid record at {offset}".corrupted())`.
//! To add context when returning [`Result`], use `bail!(err.and_raise(message("context")))`;
//! reserve [`ErrorExt::and_raise_typed()`] for [`ExnResult`]s that require an exception type.
//! When passing an existing [`Exn`], keep any context and explicit erasure inside the macro;
//! propagating an existing public [`Error`] should use `return Err(err)`.
//!
//! **Static message variant:**
//! ```rust,ignore
//! // BEFORE:
//! #[error("something went wrong")]
//! SomethingFailed,
//! // → Err(Error::SomethingFailed)
//!
//! // AFTER (returning gix_error::Result):
//! // → Err(message("something went wrong").raise())
//! ```
//!
//! **Formatted message variant:**
//! ```rust,ignore
//! // BEFORE:
//! #[error("unsupported format '{format:?}'")]
//! Unsupported { format: Format },
//! // → Err(Error::Unsupported { format })
//!
//! // AFTER (returning gix_error::Result):
//! // → Err(message!("unsupported format '{format:?}'").raise())
//! ```
//!
//! **`#[from]` / `#[error(transparent)]` variant** without a recovery contract — delete the forwarding variant;
//! at each call site, use [`ResultExt::or_raise()`] to add context:
//! ```rust,ignore
//! // BEFORE:
//! #[error(transparent)]
//! Io(#[from] std::io::Error),
//! // → something_that_returns_io_error()?  // auto-converted via From
//!
//! // AFTER (the variant is deleted):
//! // → something_that_returns_io_error()
//! //       .or_raise(|| message("context about what failed"))?
//! ```
//!
//! **`#[source]` variant with diagnostic context only** — use [`ResultExt::or_raise()`]:
//! ```rust,ignore
//! // BEFORE:
//! #[error("failed to parse config")]
//! Config(#[source] config::Error),
//! // → Err(Error::Config(err))
//!
//! // AFTER:
//! // → config_call().or_raise(|| message("failed to parse config"))?
//! ```
//!
//! **Guard / assertion** — use [`ensure!`]:
//! ```rust,ignore
//! // BEFORE:
//! if !condition {
//!     return Err(Error::SomethingFailed);
//! }
//!
//! // AFTER (returning Exn<Message>, with a validation class):
//! ensure!(condition, gix_error::validation("something went wrong"));
//!
//! // AFTER (returning Exn<Message>):
//! ensure!(condition, message("something went wrong"));
//! ```
//!
//! ## Updating the function signature
//!
//! When replacing a diagnostic-only error enum, change the return type and add the necessary imports:
//! ```rust,ignore
//! // BEFORE:
//! fn parse(input: &str) -> Result<Value, Error> { ... }
//!
//! // AFTER (public or private):
//! use gix_error::{message, ErrorExt, Result, ResultExt};
//! fn parse(input: &str) -> Result<Value> { ... }
//! ```
//! Public APIs that already expose a concrete error, such as `ExnResult<Value, SpecificError>`, retain that type.
//!
//! ## Updating tests
//!
//! Tests of diagnostic wording can use string assertions:
//! ```rust,ignore
//! assert_eq!(result.expect_err("the operation fails").to_string(), "something went wrong");
//! ```
//! Keep variant assertions for recovery contracts, and test structured payloads directly.
//!
//! For semantic checks, both [`Exn`] and [`Error`] provide [`is_retryable()`](Exn::is_retryable),
//! [`is_not_found()`](Exn::is_not_found), [`is_validation()`](Exn::is_validation),
//! [`is_corrupted()`](Exn::is_corrupted), [`is_resource_exhausted()`](Exn::is_resource_exhausted),
//! [`is_cancelled()`](Exn::is_cancelled), [`is_permission_denied()`](Exn::is_permission_denied),
//! [`is_unauthenticated()`](Exn::is_unauthenticated), [`is_conflict()`](Exn::is_conflict), and
//! [`is_unsupported()`](Exn::is_unsupported).
//! These inspect causes as well as the outermost error. `is_retryable()` requires an explicit retry classification;
//! [`Exn::can_retry()`] and [`Error::can_retry()`] additionally recognize certain I/O error kinds.
//! I/O errors with kind `NotFound`, `OutOfMemory`, `PermissionDenied`, or `Unsupported` receive corresponding
//! semantic classifications; other kinds remain unclassified. For legacy credential-challenge I/O wrappers,
//! an explicit [`Class::Unauthenticated`] anywhere in a `PermissionDenied` payload takes precedence over that
//! wrapper's native permission classification. The original I/O error remains available for downcasting;
//! independently classified permission causes in its payload remain visible.
//! `Interrupted` alone does not establish caller cancellation.
//! Retry predicates also inspect original I/O kinds, but explicit cancellation takes precedence.
//!
//! For application-level cancellation, use [`cancelled()`]. Preserve genuine I/O errors as causes.
//! Classification itself neither clears interruption state nor retries.
//! ```
//! use gix_error::ErrorExt;
//!
//! let err = gix_error::cancelled("Cancelled by user").raise();
//! assert!(err.is_cancelled());
//! assert!(!err.can_retry());
//! assert!(!err.can_retry_lenient());
//! ```
//!
//! Use [`Exn::probable_cause()`] to inspect the likely root cause. It follows a single causal path, stopping at the
//! first branch rather than choosing an arbitrary sibling. Classification markers are transparent to this selection.
//! [`Exn::classify()`] and [`Error::classify()`] expose each known classification together with its original error.
//! Custom payloads of [`std::io::Error`] are inspected too, including any nested [`Error`] trees.
//!
//! [`Message`] supplies its own diagnostic and optional classification. In contrast, [`ClassificationMarker`]
//! only supplies classification metadata. Prefer defining intrinsic classifications on error types you control:
//!
//! * For a leaf error or variant whose classification is part of its meaning, return a constant marker from
//!   [`std::error::Error::source()`]. This makes every construction site carry the classification without repeated
//!   [`tag()`] calls.
//! * Use [`tag()`] when a classification depends on the calling context, or when you cannot modify the error type.
//!   It preserves the concrete error and its diagnostic.
//!
//! For example, a caller may know that an `AlreadyExists` I/O error is retryable in its operation:
//! ```
//! use gix_error::{Class, ClassificationMarker, ErrorExt, tag};
//!
//! let err = tag(
//!     std::io::Error::from(std::io::ErrorKind::AlreadyExists),
//!     Class::Retryable,
//! ).raise();
//! assert!(err.is_retryable());
//! assert!(err.probable_cause().is::<std::io::Error>());
//! assert!(err.downcast_any_ref::<ClassificationMarker>().is_none());
//! let classification = err.classify().next().expect("the tag is first");
//! assert!(classification.error().is::<std::io::Error>());
//! assert_eq!(classification.io_kind(), Some(std::io::ErrorKind::AlreadyExists));
//! ```
//!
//! Custom error types preserve classifications by exposing their immediate cause as `Some(inner)` from
//! [`std::error::Error::source()`]. Forwarding to `inner.source()` instead can hide a classification carried by
//! `inner` itself. A custom leaf error can borrow a constant such as [`ClassificationMarker::NOT_FOUND`]
//! as its source to preserve its classification without defining a static or adding a generic category to its diagnostic:
//! ```
//! use gix_error::{ClassificationMarker, ErrorExt};
//!
//! #[derive(Debug)]
//! struct MissingObject;
//!
//! impl std::fmt::Display for MissingObject {
//!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//!         f.write_str("the requested object is missing from the object database")
//!     }
//! }
//!
//! impl std::error::Error for MissingObject {
//!     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
//!         Some(const { &ClassificationMarker::NOT_FOUND })
//!     }
//! }
//!
//! let err = MissingObject.raise();
//! assert!(err.is_not_found());
//! assert!(err.probable_cause().is::<MissingObject>());
//! assert!(err.classify().next().expect("a classified owner").error().is::<MissingObject>());
//! ```
//! Use classification predicates rather than downcasting to [`Message`] just to recognize
//! a category: diagnostic iterators and downcasts skip all classification markers. Exception and test reports
//! omit their wrappers too, while raw [`std::error::Error::source()`] chains retain them. Genuine classified errors
//! remain causal and can still be downcast to inspect their payloads. When storing an [`Exn`] in a custom error, convert it with
//! [`Exn::into_error()`] so the source can expose its complete tree.
//!
//! Record offending input with [`Message::with_input()`], then inspect the documented [metadata](Exn::metadata()) key:
//! ```
//! use gix_error::{ErrorExt, MetadataValue};
//!
//! let err = gix_error::validation("invalid input").with_input(b"bad".as_slice()).raise();
//! let values = err.metadata().find(|values| values.contains_key("input")).expect("input context");
//! assert_eq!(values["input"], MetadataValue::Bytes(b"bad".as_slice().into()));
//! ```
//!
//! ## Matching a specific failure
//!
//! Downcast to the operation's error enum and match a variant when a broad category such as [`Class::NotFound`]
//! isn't specific enough for recovery. The enum retains structured data and identifies the condition independently
//! of diagnostic wording. For intrinsic classifications on leaf variants of an enum you define, prefer an exhaustive
//! match on `self` in [`std::error::Error::source()`], returning a constant marker as below. Each new variant then
//! requires an explicit classification choice. Wrapping, erasure, and conversion to [`Error`] preserve the
//! classification, so callers do not need to repeat it with [`tag()`].
//!
//! ```
//! use gix_error::{ErrorExt, message};
//!
//! mod merge {
//!     use gix_error::ClassificationMarker;
//!
//!     #[derive(Debug)]
//!     #[non_exhaustive]
//!     pub enum Error {
//!         MissingBinaryMergeResult,
//!     }
//!
//!     impl std::fmt::Display for Error {
//!         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//!             match self {
//!                 Self::MissingBinaryMergeResult => f.write_str("The binary merge result could not be selected"),
//!             }
//!         }
//!     }
//!     impl std::error::Error for Error {
//!         fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
//!             match self {
//!                 Self::MissingBinaryMergeResult => Some(const { &ClassificationMarker::NOT_FOUND }),
//!             }
//!         }
//!     }
//! }
//!
//! fn recover(err: gix_error::Error) -> gix_error::Result<()> {
//!     match err.downcast_any_ref::<merge::Error>() {
//!         Some(merge::Error::MissingBinaryMergeResult) => Ok(()), // Apply the caller's fallback.
//!         _ => Err(err), // Preserve unfamiliar variants and all other errors.
//!     }
//! }
//!
//! let err = merge::Error::MissingBinaryMergeResult.and_raise(message("Tree merge failed"));
//!
//! assert!(err.is_not_found(), "the variant supplies its intrinsic classification");
//! assert!(
//!     matches!(err.downcast_any_ref::<merge::Error>(), Some(merge::Error::MissingBinaryMergeResult)),
//!     "the classified variant remains available for recovery"
//! );
//! recover(err)?;
//! # Ok::<(), gix_error::Error>(())
//! ```
//!
//! Downcasting and [`types::Classification::error()`] retain concrete subjects through contexts and [`Error`] conversion.
//! Matching one cause does not make other failures in an aggregate ignorable.
//!
//! # Common Pitfalls
//!
//! ## Don't use `.erased()` to change the `Exn` type parameter
//!
//! [`Exn::raise()`] already nests the current `Exn<E>` as a child of a new `Exn<T>` without prior erasure.
//! On native errors, use [`ErrorExt::and_raise()`] for an [`Error`], or
//! [`ErrorExt::and_raise_typed()`] for a typed exception.
//!
//! Use [`.erased()`](Exn::erased) when you need a type-erased `Exn` (no type parameter),
//! e.g. when combining different failures in an exception tree. Otherwise return [`Result<T>`](Result).
//!
//! ## Don't use `.raise_all()` with a single error
//!
//! [`Exn::raise_all()`] is meant for creating error trees with *multiple* causes.
//! If you only have a single causing error, use [`.or_raise()`](ResultExt::or_raise) instead:
//! ```rust,ignore
//! // WRONG — raise_all() is for multiple causes, not a single one:
//! result.map_err(|e| message("context").raise_all(Some(e.raise_typed())))?;
//!
//! // RIGHT — or_raise() wraps the error with context directly:
//! result.or_raise(|| message("context"))?;
//! ```
//!
//! ## Avoid conversions between diagnostic-only helpers
//!
//! Use [`Result`] throughout a call chain unless callers need typed exceptions or exception-tree operations.
//! Helpers can then return their callee's result directly:
//! ```
//! use gix_error::{message, Result, ResultExt};
//!
//! fn parse_count(input: &str) -> Result<u64> {
//!     input.parse::<u64>().or_raise(|| message("could not parse count"))
//! }
//!
//! pub fn count(input: &str) -> Result<u64> {
//!     parse_count(input)
//! }
//! assert_eq!(count("42")?, 42);
//! # Ok::<(), gix_error::Error>(())
//! ```
//! Concrete public exception signatures remain typed. Use [`ResultExt::or_error()`] when a callee returns such
//! an exception and the caller returns [`Result`]. [`Error`] implements [`std::error::Error`]; [`Exn`] does not.
//!
//! # Supporting types
//!
//! Frequently used error types, extension traits, result aliases, and constructors are available at the crate root.
//! Utility types for flattened chains, classification, and diagnostic display live in [`types`]. Exception frames
//! and the default type-erasure marker live in [`exn`]; [`Exn`] and its extension traits are only exported at the root.
//!
//! # Feature Flags
#![cfg_attr(
    all(doc, feature = "document-features"),
    doc = ::document_features::document_features!()
)]
//! # Why not `anyhow`?
//!
//! `anyhow` is a proven and optimized library, and it would certainly suffice for an error-chain based approach
//! where users are expected to downcast to concrete types.
//!
//! What's missing though is `track-caller` which will always capture the location of error instantiation, along with
//! compatibility for error trees, which are happening when multiple calls are in flight during concurrency.
//!
//! [`Exn`] intentionally does not implement `std::error::Error`; the default helpers return [`Error`], which does.
//!
//! `exn` is much less optimized, but also costs only a `Box` on the stack,
//! which in any case is a step up from `thiserror` which exposed a lot of heft to the stack.
#![deny(missing_docs, unsafe_code)]
pub mod exn;
pub mod types;

#[cfg(feature = "bstr")]
pub use bstr;
pub use exn::{
    ext::{BoxedResultExt, ErrorExt, OptionExt, ResultExt},
    impls::Exn,
};

/// An error type that wraps an inner type-erased boxed `std::error::Error` or an `Exn` frame.
///
/// In that, it's similar to `anyhow`, but with support for tracking the call site and trees of errors.
///
/// # Native error sources
///
/// [`Error::from_error()`] retains the concrete error and its native [`source()`](std::error::Error::source) chain.
/// Use [`Error::downcast_any_ref()`] or [`Error::iter_errors()`] to inspect the original types, including sources
/// within nested [`Error`] values. This also applies when the `auto-chain-error` feature is enabled.
///
/// In tree mode, standard `source()` traversal prefers the stored error's native source; otherwise it follows the
/// first explicitly raised child. Nonleaf explicit children are exposed through owning source boundaries so traversal
/// retains their descendants. These boundaries display only their current diagnostic, including with alternate Display,
/// while explicit leaves and native sources retain their raw concrete payloads. Raw standard-source downcasts can thus
/// encounter wrappers; use [`Error::downcast_any_ref()`] or [`Error::iter_errors()`] for typed inspection of the complete
/// tree. Standard traversal follows one path, not every branch; use [`Exn::into_chain()`] for a flattened source chain.
///
/// # The `auto-chain-error` feature
///
/// If it's enabled, this type is merely a wrapper around [`ChainedError`](types::ChainedError). This happens automatically
/// so applications that require this don't have to go through an extra conversion.
///
/// When both the `tree-error` and `auto-chain-error` features are enabled, the `tree-error`
/// behavior takes precedence and this type uses the tree-based representation.
///
/// With `auto-chain-error`, [`Debug`](std::fmt::Debug) reports the complete diagnostic chain,
/// so returning [`Result`] from `main()` retains the underlying causes. Caller locations are printed only with the
/// `error-print-location` feature, including for errors returned from `main()`. Alternate Debug (`{error:#?}`) omits them.
/// Normal [`Display`](std::fmt::Display) shows the root diagnostic; alternate Display (`{error:#}`) joins the
/// complete chain with `: ` and omits locations, suitable for single-line error messages.
pub struct Error {
    #[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
    inner: error::Inner,
    #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
    inner: types::ChainedError,
}

fn root_error_eq(mut error: &(dyn std::error::Error + 'static), other: &str) -> bool {
    while let Some(nested) = error.downcast_ref::<Error>() {
        error = nested.error();
    }
    error.to_string() == other
}

impl PartialEq<str> for Error {
    fn eq(&self, other: &str) -> bool {
        root_error_eq(self.error(), other)
    }
}

impl PartialEq<&str> for Error {
    fn eq(&self, other: &&str) -> bool {
        <Self as PartialEq<str>>::eq(self, other)
    }
}

impl PartialEq<String> for Error {
    fn eq(&self, other: &String) -> bool {
        <Self as PartialEq<str>>::eq(self, other)
    }
}

/// The result type for erased or message-based errors in plumbing and porcelain crates.
///
/// Uses [`Error`] and defaults to unit success. Public and private implementations use this alias unless
/// callers need a concrete error type or exception-tree operations provided by [`ExnResult`] or [`ExnMessageResult`].
/// Public APIs that already expose concrete exception types retain those types.
pub type Result<T = ()> = std::result::Result<T, Error>;

/// A result with an [`Exn<E>`](Exn) error, defaulting to unit success and an erased error type.
///
/// `ExnResult<T>` uses the same [`exn::Untyped`] marker as bare [`Exn`]. Specify `E` to retain a
/// concrete error type; [`ExnMessageResult`] is the shorthand for message contexts. Use these aliases when callers
/// need typed exceptions or exception-tree operations; other public and private code uses [`Result`].
/// All standard result operations and [`ResultExt`] methods remain available. Use [`ResultExt::or_erased()`] for
/// callbacks requiring erased exceptions, and `?` to propagate exceptions into [`Error`].
/// Preserve this alias in public signatures that already expose a concrete error type.
///
/// ```
/// use gix_error::{ErrorExt, ExnResult};
///
/// fn parse_count(input: &str) -> ExnResult<u64, std::num::ParseIntError> {
///     input.parse::<u64>().map_err(ErrorExt::raise_typed)
/// }
///
/// let error = parse_count("not a count").expect_err("the count contains letters");
/// assert_eq!(
///     error.error().kind(),
///     &std::num::IntErrorKind::InvalidDigit,
///     "callers can inspect the concrete error without downcasting"
/// );
/// ```
pub type ExnResult<T = (), E = exn::Untyped> = std::result::Result<T, Exn<E>>;

/// A result with a [`Message`] exception, defaulting to unit success.
///
/// This is [`ExnResult<T, Message>`](ExnResult). Use it when callers need direct access to the [`Message`]
/// context or exception-tree operations. Other message-based public and private APIs use [`Result<T>`](Result).
/// Construct it with [`ResultExt::or_raise_typed()`], [`ErrorExt::raise_typed()`], or [`bail!`].
///
/// ```
/// use gix_error::{bail, ExnMessageResult};
///
/// fn validate(ready: bool) -> ExnMessageResult {
///     if !ready {
///         bail!("not ready");
///     }
///     Ok(())
/// }
///
/// validate(true)?;
/// assert_eq!(validate(false).expect_err("not ready").error().message, "not ready");
/// # Ok::<(), gix_error::Error>(())
/// ```
pub type ExnMessageResult<T = ()> = ExnResult<T, Message>;

mod test;
pub use test::{TestError, TestResult};

mod error;
pub use error::{Class, classify};

/// Various kinds of concrete errors that implement [`std::error::Error`].
mod concrete;

pub use concrete::classify::{ClassificationMarker, ResourceExhaustionKind, tag};
pub use concrete::message::message;
pub use concrete::metadata::{
    Message, Metadata, MetadataValue, allocation_failure, allocation_limit, cancelled, conflict, corruption, not_found,
    permission_denied, resource_exhaustion, retryable, unauthenticated, unsupported, validation,
};

mod location;
pub(crate) use location::write as write_location;