From c2259b5fbc979ea6e7b9eda4d7da2bf39b59a2a2 Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 14:54:37 +0200 Subject: [PATCH 1/9] docs: clarify how to write error documentation --- doc/WritingErrorsGuide.en.md | 259 +++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 120 deletions(-) diff --git a/doc/WritingErrorsGuide.en.md b/doc/WritingErrorsGuide.en.md index 799e0cd..ac00aaa 100644 --- a/doc/WritingErrorsGuide.en.md +++ b/doc/WritingErrorsGuide.en.md @@ -1,205 +1,224 @@ -# Writing Errors Guide +# Writing Error Documentation 🌍 **Languages:** 🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./WritingErrorsGuide.fr.md) -FirstClassErrors gives you tools. -This guide helps you use them in a consistent and meaningful way. +A documented error should help someone understand a failure without opening the source code first. -The goal is not just to throw exceptions, but to **express errors clearly, precisely, and usefully** for humans. +This guide explains how to describe the **stable meaning** of an error: its code, title, description, rule, diagnostics, and examples. Runtime messages are covered separately in [Writing Error Messages](WritingErrorMessages.en.md). -> The prose you write below (title, description, rule, causes, 
) can be authored as plain strings or sourced from localized resources so the catalog is generated in several languages — see [Internationalization](Internationalization.en.md). +> Documentation text may be authored as literals or read from localized resources. See [Internationalization](Internationalization.en.md). -## 🎯 1. Think in *error situations*, not just failures +## The model in one minute -Each documented error should represent: +Each documented factory answers six questions: -> **one specific situation in which the system cannot proceed as expected** +| Element | Question it answers | +| --- | --- | +| Error code | How can software identify this situation? | +| Title | What happened, in a few words? | +| Description | What does this error mean? | +| Rule | What should normally be true? | +| Diagnostics | What may explain it, and where should investigation start? | +| Examples | What does a real occurrence look like? | -Avoid vague or generic errors such as: +The goal is not to describe every technical detail. It is to capture the knowledge that remains useful across logs, support investigations, releases, and refactorings. -* “Invalid operation” -* “Processing error” -* “Unexpected issue” +## 1. Start with one precise error situation -Prefer precise, contextual situations: +A factory should represent one situation in which the system cannot continue as expected. -* “Amount currency mismatch” -* “Temperature below absolute zero” -* “Transaction date outside statement period” +Avoid broad categories such as: -An error should describe *what went wrong in domain terms*, not how the system reacted. +- `INVALID_OPERATION` +- `PROCESSING_ERROR` +- `UNEXPECTED_FAILURE` -## đŸ·ïž 2. Writing a good **error code** +Prefer situations that a developer or support engineer can recognize immediately: -The error code is the stable, machine-readable identifier. +- `AMOUNT_CURRENCY_MISMATCH` +- `TEMPERATURE_BELOW_ABSOLUTE_ZERO` +- `TRANSACTION_DATE_OUTSIDE_STATEMENT_PERIOD` -Good practices: +A useful test is: -* Use a **clear domain scope** - `AMOUNT_CURRENCY_MISMATCH` -* Keep it **stable over time** -* Avoid technical details (no class names, no method names) -* One code = one documented error situation +> Could two occurrences with genuinely different meanings share this documentation without making it vague? -Think of the error code as an API contract. +If the answer is no, they probably need separate factories and codes. -## đŸ§Ÿ 3. Writing the **Title** +## 2. Choose a stable error code -The title is a short human summary. +The code is the machine-readable identity of the error. -It should: - -* be concise -* describe the situation, not the consequence -* avoid technical wording +Use `UPPER_SNAKE_CASE`, include enough domain scope to avoid ambiguity, and keep the code independent from class names or implementation details. Good: -* “Amount currency mismatch” -* “Temperature below absolute zero” +```text +AMOUNT_CURRENCY_MISMATCH +``` Avoid: -* “InvalidAmountOperationError” -* “Operation failed” - -## 📝 4. Writing the **Description** +```text +INVALID_AMOUNT_OPERATION_ERROR +ADD_METHOD_FAILED +``` -The description explains what the error means. +Treat the code as a contract. Clients, dashboards, alerts, and support procedures may depend on it. Renaming or removing it is therefore a breaking change; see [Catalog Versioning](CatalogVersioning.en.md). -A good pattern is: +## 3. Write a title that names the situation -> “This error occurs trying to
” +The title is a short human-readable label. -or +Good titles: -> “This error occurs when
” +- “Amount currency mismatch” +- “Temperature below absolute zero” +- “Transaction date outside statement period” -You may choose the phrasing that fits best, but remain consistent within the project. Consistency in wording improves readability and makes the documentation feel cohesive. +Avoid titles that merely announce failure: -The description should: +- “Operation failed” +- “Invalid value” +- “Unexpected error” -* describe the situation in plain language -* be understandable by someone who does not know the code -* explain *what* happened, not *how the system reacted* +A title should still make sense when displayed in a catalog index without its description. -## 📏 5. Writing the **Rule** +## 4. Explain the meaning in plain language -The rule expresses the invariant or business constraint. +The description explains when the error occurs and what the situation means. -It should: +A reliable pattern is: -* be stated as a general truth -* describe what must always hold +> “This error occurs when
” -Examples: +Write for someone who understands the business system but may not know the implementation. Describe the situation, not the stack trace, class, method, or exception-handling behavior. -* “All monetary operations must involve amounts expressed in the same currency.” -* “Temperature cannot go below absolute zero.” +Good: -If no explicit rule exists, it is acceptable to omit it. +> “This error occurs when an operation combines monetary amounts expressed in different currencies.” -## 🔍 6. Writing a good **Cause** +Avoid: -A cause describes a plausible reason the error occurred. +> “This exception is thrown by `Amount.Add` when the currency fields are different.” -It should: +## 5. State the violated rule -* describe a **state or condition**, not an action -* avoid blaming -* be specific enough to guide investigation +The rule expresses the invariant or constraint that should normally hold. -Good: +Write it as a general truth: -* “Amounts were used in a monetary operation without having been converted to the same currency.” +> “All monetary operations must involve amounts expressed in the same currency.” -Avoid: +A rule should not repeat the description. The description says what happened; the rule says what must be true. -* “The developer forgot to convert the currency.” -* “Fix the data.” +Omit the rule when no meaningful invariant exists rather than inventing one. -## 🧭 7. Writing a good **AnalysisLead** +## 6. Write diagnostics as hypotheses -An analysis lead suggests where to look first. +A diagnostic contains three parts: -It should: +| Part | Purpose | +| --- | --- | +| Cause | A plausible state or condition that may explain the error | +| Origin | Whether that cause is internal, external, or either | +| Analysis lead | Where investigation should start | -* start with a neutral verb such as *Verify*, *Check*, *Review* -* guide investigation, not define procedures -* avoid ticketing or support process details +Causes are hypotheses, not proven root causes. Describe conditions without assigning blame. -Good: +Good cause: -* “Verify whether all amounts involved in the operation were converted to a common currency before being used together.” +> “Amounts were used before they had been converted to a common currency.” Avoid: -* “Open a ticket.” -* “Contact team X.” +> “The developer forgot to convert the amounts.” -## đŸ§Ș 8. Writing good **Examples** +A useful analysis lead starts with a neutral verb such as *Check*, *Verify*, or *Review*: -Examples illustrate how the error appears in practice. +> “Verify whether every amount was converted to the operation currency before calculation.” -They should: +Do not encode organizational procedures such as “open a ticket” or “contact team X”. Those processes change independently from the application. -* use realistic values -* be simple and clear -* highlight the rule violation, not edge cases +## 7. Use examples to make the rule visible -Examples are not tests — they are educational. +Examples should use simple, realistic values that make the violation obvious. -## 🧠 9. Separate domain from technical noise +```csharp +.WithExamples( + () => CurrencyMismatch( + new Amount(127.33m, Currency.EUR), + new Amount(84.10m, Currency.USD))) +``` -Error documentation should focus on: +Examples are educational catalog content, not boundary-value tests. Prefer one or two representative occurrences over pathological values. -* domain meaning -* violated rules -* plausible causes +Because the example invokes the real factory, it also keeps the generated documentation connected to the actual runtime error. -Avoid leaking: +## Complete example -* stack traces -* framework details -* internal class names +```csharp +[ProvidesErrorsFor(nameof(Amount))] +public static class InvalidAmountOperationError { -## đŸ—‚ïž 10. The three messages an error carries + [DocumentedBy(nameof(CurrencyMismatchDocumentation))] + internal static DomainError CurrencyMismatch(Amount left, Amount right) { + return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: $"Cannot combine {left.Currency} and {right.Currency} amounts: {left} and {right}.") + .WithPublicMessage( + shortMessage: "The amounts use different currencies.", + detailedMessage: "All amounts in this operation must use the same currency."); + } -The elements above describe the **documentation** of an error. In addition, each factory sets the three messages the error carries at runtime, through the staged builder `Type.Create(code, diagnosticMessage, 
).WithPublicMessage(shortMessage, detailedMessage)`: + private static ErrorDocumentation CurrencyMismatchDocumentation() { + return DescribeError + .WithTitle("Amount currency mismatch") + .WithDescription("This error occurs when an operation combines monetary amounts expressed in different currencies.") + .WithRule("All monetary operations must involve amounts expressed in the same currency.") + .WithDiagnostic( + "Amounts were used before they had been converted to a common currency.", + ErrorOrigin.Internal, + "Verify whether every amount was converted to the operation currency before calculation.") + .AndDiagnostic( + "An external request supplied amounts in incompatible currencies.", + ErrorOrigin.External, + "Check the currencies supplied by the caller and the currency expected by the operation.") + .WithExamples(() => CurrencyMismatch( + new Amount(127.33m, Currency.EUR), + new Amount(84.10m, Currency.USD))); + } -| Message | Mandatory | Audience | Exposure | -| ------------------- | --------- | ----------------------- | ---------------------------------------------------------------------------------------------------- | -| `ShortMessage` | Yes | End users / API clients | Safe public summary (e.g. an RFC 9457 `title`). | -| `DetailedMessage` | No | End users / API clients | Controlled public detail, exposed only when the application chooses to (e.g. an RFC 9457 `detail`); never sensitive or internal. | -| `DiagnosticMessage` | Yes | Logs, support, devs | Internal diagnostic detail (identifiers, offending values, internal state); never exposed to external clients by default. | + private static class Code { + public static readonly ErrorCode CurrencyMismatch = + ErrorCode.Create("AMOUNT_CURRENCY_MISMATCH"); + } +} +``` -Keep the diagnostic message rich and technical; keep the public messages safe and free of internal detail. `error.ToException()` uses the diagnostic message as the exception's `Message`. When the error is exposed over HTTP, its `Code` is the natural RFC 9457 `type`: surface it as a stable URI such as `urn:problem:{service}:{code}`, where `{code}` is the error code in lowercase kebab-case — for example `urn:problem:temperature-simulator:temperature-below-absolute-zero` or `urn:problem:banking-api:money-transfer-amount-not-positive` — so clients can branch on the problem type (`type` and `status` remain the application's concern). +The documentation describes the stable error situation. The factory separately creates a concrete occurrence with runtime messages and values. -## 🏁 Summary +## Review checklist -When writing errors: +Before accepting new error documentation, verify that: -| Element | Purpose | -| ------------- | ------------------------ | -| Error code | Stable identifier | -| Short message | Safe public summary | -| Detailed message | Controlled public detail (optional) | -| Diagnostic message | Internal, for logs and support | -| Title | Short human summary | -| Description | What the error means | -| Rule | The violated invariant | -| Cause | Why it may have happened | -| Analysis lead | Where to investigate | -| Examples | How it looks in reality | +- the factory represents one precise situation; +- the code is specific, stable, and written in `UPPER_SNAKE_CASE`; +- the title names the situation rather than announcing failure; +- the description is understandable without reading the implementation; +- the rule is a genuine invariant, or is omitted; +- diagnostic causes are plausible conditions rather than accusations; +- analysis leads guide investigation without prescribing support workflow; +- examples are simple, realistic, and call the documented factory; +- technical noise and sensitive data are absent. -Well-written errors are not just thrown. -They become part of the **shared understanding of how the system works — and fails.** +For choosing public and internal runtime text, continue with [Writing Error Messages](WritingErrorMessages.en.md). For a compact project-wide review list, see [Best Practices](BestPractices.en.md). ---
-← Error Context Guide · ↑ Table of contents · Usage Patterns → +← Error Context Guide · ↑ Table of contents · Writing Error Messages →
--- \ No newline at end of file From 5e10574b19533b4d80b9b021e33fafc0ec82ac70 Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 14:56:10 +0200 Subject: [PATCH 2/9] docs: clarify how to write error documentation --- doc/WritingErrorsGuide.fr.md | 258 +++++++++++++++++++---------------- 1 file changed, 140 insertions(+), 118 deletions(-) diff --git a/doc/WritingErrorsGuide.fr.md b/doc/WritingErrorsGuide.fr.md index cc09d9e..bd4edd2 100644 --- a/doc/WritingErrorsGuide.fr.md +++ b/doc/WritingErrorsGuide.fr.md @@ -1,202 +1,224 @@ -# Guide d’écriture des erreurs +# Écrire la documentation d’une erreur -🌍 **Langues:** +🌍 **Langues :** 🇬🇧 [English](./WritingErrorsGuide.en.md) | đŸ‡«đŸ‡· Français (ce fichier) -FirstClassErrors vous fournit des outils. Ce guide vous aide Ă  les utiliser de maniĂšre cohĂ©rente et porteuse de sens. +Une erreur documentĂ©e doit permettre de comprendre une dĂ©faillance sans commencer par ouvrir le code source. -L’objectif n’est pas seulement de lever des exceptions, mais d’**exprimer les erreurs de façon claire, prĂ©cise et utile** pour des humains. +Ce guide explique comment dĂ©crire le **sens stable** d’une erreur : son code, son titre, sa description, sa rĂšgle, ses diagnostics et ses exemples. Les messages portĂ©s Ă  l’exĂ©cution sont traitĂ©s sĂ©parĂ©ment dans [Écrire les messages d’une erreur](WritingErrorMessages.fr.md). -> La prose que vous rĂ©digez ci-dessous (titre, description, rĂšgle, causes, 
) peut ĂȘtre Ă©crite en dur ou lue depuis des ressources localisĂ©es, afin de gĂ©nĂ©rer le catalogue en plusieurs langues — voir [Internationalisation](Internationalisation.fr.md). +> Le texte de documentation peut ĂȘtre Ă©crit en dur ou lu depuis des ressources localisĂ©es. Voir [Internationalisation](Internationalisation.fr.md). -## 🎯 1. Pensez en *situations d’erreur*, pas seulement en Ă©checs +## Le modĂšle en une minute -Chaque erreur documentĂ©e doit reprĂ©senter : +Chaque factory documentĂ©e rĂ©pond Ă  six questions : -> **une situation prĂ©cise dans laquelle le systĂšme ne peut pas continuer comme prĂ©vu** +| ÉlĂ©ment | Question Ă  laquelle il rĂ©pond | +| --- | --- | +| Code d’erreur | Comment le logiciel identifie-t-il cette situation ? | +| Titre | Que s’est-il passĂ©, en quelques mots ? | +| Description | Que signifie cette erreur ? | +| RĂšgle | Qu’est-ce qui devrait normalement ĂȘtre vrai ? | +| Diagnostics | Qu’est-ce qui peut l’expliquer et oĂč commencer l’analyse ? | +| Exemples | À quoi ressemble une occurrence rĂ©elle ? | -Évitez les erreurs vagues ou gĂ©nĂ©riques comme : +L’objectif n’est pas de dĂ©crire tous les dĂ©tails techniques. Il est de capturer une connaissance qui reste utile dans les logs, les investigations du support, les releases et les refactorings. -* « OpĂ©ration invalide » -* « Erreur de traitement » -* « ProblĂšme inattendu » +## 1. Partir d’une situation d’erreur prĂ©cise -PrĂ©fĂ©rez des situations prĂ©cises et contextualisĂ©es : +Une factory doit reprĂ©senter une situation unique dans laquelle le systĂšme ne peut pas continuer comme prĂ©vu. -* « IncohĂ©rence de devise des montants » -* « TempĂ©rature sous le zĂ©ro absolu » -* « Date de transaction hors pĂ©riode du relevĂ© » +Évitez les catĂ©gories trop larges : -Une erreur doit dĂ©crire *ce qui s’est mal passĂ© en termes mĂ©tier*, pas la rĂ©action du systĂšme. +- `INVALID_OPERATION` +- `PROCESSING_ERROR` +- `UNEXPECTED_FAILURE` -## đŸ·ïž 2. Écrire un bon **code d’erreur** +PrĂ©fĂ©rez des situations qu’un dĂ©veloppeur ou le support peut reconnaĂźtre immĂ©diatement : -Le code d’erreur est l’identifiant stable, lisible par machine. +- `AMOUNT_CURRENCY_MISMATCH` +- `TEMPERATURE_BELOW_ABSOLUTE_ZERO` +- `TRANSACTION_DATE_OUTSIDE_STATEMENT_PERIOD` -Bonnes pratiques : +Un bon test consiste Ă  demander : -* Utiliser un **pĂ©rimĂštre mĂ©tier clair** `AMOUNT_CURRENCY_MISMATCH` -* Le garder **stable dans le temps** -* Éviter les dĂ©tails techniques (pas de noms de classes, pas de noms de mĂ©thodes) -* Un code = une situation d’erreur documentĂ©e +> Deux occurrences de sens rĂ©ellement diffĂ©rent pourraient-elles partager cette documentation sans la rendre vague ? -ConsidĂ©rez le code d’erreur comme un contrat d’API. +Si la rĂ©ponse est non, elles nĂ©cessitent probablement des factories et des codes distincts. -## đŸ§Ÿ 3. Écrire le **Title** +## 2. Choisir un code d’erreur stable -Le titre est un rĂ©sumĂ© humain court. +Le code est l’identitĂ© de l’erreur, lisible par machine. -Il doit : - -* ĂȘtre concis -* dĂ©crire la situation, pas la consĂ©quence -* Ă©viter le vocabulaire technique +Utilisez `UPPER_SNAKE_CASE`, incluez suffisamment de contexte mĂ©tier pour Ă©viter l’ambiguĂŻtĂ© et rendez le code indĂ©pendant des noms de classes ou des dĂ©tails d’implĂ©mentation. Bon : -* « IncohĂ©rence de devise des montants » -* « TempĂ©rature sous le zĂ©ro absolu » +```text +AMOUNT_CURRENCY_MISMATCH +``` À Ă©viter : -* « InvalidAmountOperationError » -* « L’opĂ©ration a Ă©chouĂ© » - -## 📝 4. Écrire la **Description** +```text +INVALID_AMOUNT_OPERATION_ERROR +ADD_METHOD_FAILED +``` -La description explique la signification de l’erreur. +ConsidĂ©rez le code comme un contrat. Des clients, dashboards, alertes ou procĂ©dures de support peuvent en dĂ©pendre. Le renommer ou le supprimer constitue donc un changement cassant ; voir [Versionnage du catalogue](CatalogVersioning.fr.md). -Un bon schĂ©ma est : +## 3. Écrire un titre qui nomme la situation -> « Cette erreur survient en essayant de
 » +Le titre est un libellĂ© humain court. -ou +Bons titres : -> « Cette erreur survient lorsque
 » +- « IncohĂ©rence de devise des montants » +- « TempĂ©rature sous le zĂ©ro absolu » +- « Date de transaction hors pĂ©riode du relevĂ© » -Vous pouvez choisir la formulation qui convient le mieux, mais restez cohĂ©rent au sein du projet. La cohĂ©rence dans la formulation amĂ©liore la lisibilitĂ© et rend la documentation plus homogĂšne. +Évitez les titres qui annoncent seulement un Ă©chec : -La description doit : +- « L’opĂ©ration a Ă©chouĂ© » +- « Valeur invalide » +- « Erreur inattendue » -* dĂ©crire la situation en langage simple -* ĂȘtre comprĂ©hensible par quelqu’un qui ne connaĂźt pas le code -* expliquer *ce qui s’est passĂ©*, pas *comment le systĂšme a rĂ©agi* +Le titre doit rester comprĂ©hensible lorsqu’il apparaĂźt seul dans l’index d’un catalogue. -## 📏 5. Écrire la **rĂšgle** +## 4. Expliquer le sens en langage simple -La rĂšgle exprime l’invariant ou la contrainte mĂ©tier. +La description explique quand l’erreur survient et ce que la situation signifie. -Elle doit : +Un schĂ©ma fiable est : -* ĂȘtre formulĂ©e comme une vĂ©ritĂ© gĂ©nĂ©rale -* dĂ©crire ce qui doit toujours ĂȘtre respectĂ© +> « Cette erreur survient lorsque
 » -Exemples : +Écrivez pour une personne qui comprend le systĂšme mĂ©tier sans nĂ©cessairement connaĂźtre son implĂ©mentation. DĂ©crivez la situation, pas la stack trace, la classe, la mĂ©thode ou le mĂ©canisme d’exception. -* « Toutes les opĂ©rations monĂ©taires doivent impliquer des montants exprimĂ©s dans la mĂȘme devise. » -* « La tempĂ©rature ne peut pas descendre sous le zĂ©ro absolu. » +Bon : -S’il n’y a pas de rĂšgle explicite, il est acceptable d’omettre cette section. +> « Cette erreur survient lorsqu’une opĂ©ration combine des montants exprimĂ©s dans des devises diffĂ©rentes. » -## 🔍 6. Écrire une bonne **Cause** +À Ă©viter : -Une cause dĂ©crit une raison plausible de l’erreur. +> « Cette exception est levĂ©e par `Amount.Add` lorsque les champs de devise sont diffĂ©rents. » -Elle doit : +## 5. Énoncer la rĂšgle violĂ©e -* dĂ©crire un **Ă©tat ou une condition**, pas une action -* Ă©viter toute accusation -* ĂȘtre suffisamment prĂ©cise pour guider l’investigation +La rĂšgle exprime l’invariant ou la contrainte qui devrait normalement ĂȘtre respectĂ©. -Bon : +Formulez-la comme une vĂ©ritĂ© gĂ©nĂ©rale : -* « Des montants ont Ă©tĂ© utilisĂ©s dans une opĂ©ration monĂ©taire sans avoir Ă©tĂ© convertis dans la mĂȘme devise. » +> « Toutes les opĂ©rations monĂ©taires doivent impliquer des montants exprimĂ©s dans la mĂȘme devise. » -À Ă©viter : +La rĂšgle ne doit pas rĂ©pĂ©ter la description. La description dit ce qui s’est passĂ© ; la rĂšgle dit ce qui doit ĂȘtre vrai. -* « Le dĂ©veloppeur a oubliĂ© de convertir la devise. » -* « Corriger les donnĂ©es. » +Lorsqu’aucun invariant pertinent n’existe, omettez la rĂšgle plutĂŽt que d’en inventer une. -## 🧭 7. Écrire une bonne **piste d’analyse** (AnalysisLead) +## 6. Écrire les diagnostics comme des hypothĂšses -Une piste d’analyse suggĂšre oĂč regarder en premier. +Un diagnostic contient trois Ă©lĂ©ments : -Elle doit : +| ÉlĂ©ment | RĂŽle | +| --- | --- | +| Cause | Un Ă©tat ou une condition plausible pouvant expliquer l’erreur | +| Origine | Une cause interne, externe ou potentiellement les deux | +| Piste d’analyse | L’endroit oĂč commencer l’investigation | -* commencer par un verbe neutre comme *VĂ©rifier*, *ContrĂŽler*, *Examiner* -* guider l’investigation, pas dĂ©finir des procĂ©dures -* Ă©viter les dĂ©tails de processus de support +Les causes sont des hypothĂšses, pas des causes racines prouvĂ©es. DĂ©crivez des conditions sans attribuer de faute. -Bon : +Bonne cause : -* « VĂ©rifier si tous les montants impliquĂ©s ont Ă©tĂ© convertis dans une devise commune avant d’ĂȘtre utilisĂ©s ensemble. » +> « Des montants ont Ă©tĂ© utilisĂ©s avant d’avoir Ă©tĂ© convertis dans une devise commune. » À Ă©viter : -* « Ouvrir un ticket. » -* « Contacter l’équipe X. » +> « Le dĂ©veloppeur a oubliĂ© de convertir les montants. » -## đŸ§Ș 8. Écrire de bons **Exemples** +Une bonne piste d’analyse commence par un verbe neutre comme *VĂ©rifier*, *ContrĂŽler* ou *Examiner* : -Les exemples illustrent l’apparence de l’erreur en pratique. +> « VĂ©rifier que chaque montant a Ă©tĂ© converti dans la devise de l’opĂ©ration avant le calcul. » -Ils doivent : +N’encodez pas de procĂ©dures organisationnelles comme « ouvrir un ticket » ou « contacter l’équipe X ». Ces processus Ă©voluent indĂ©pendamment de l’application. -* utiliser des valeurs rĂ©alistes -* ĂȘtre simples et clairs -* mettre en Ă©vidence la violation de la rĂšgle, pas des cas extrĂȘmes +## 7. Utiliser les exemples pour rendre la rĂšgle visible -Les exemples ne sont pas des tests — ils ont un rĂŽle pĂ©dagogique. +Les exemples doivent utiliser des valeurs simples et rĂ©alistes qui rendent la violation Ă©vidente. -## 🧠 9. SĂ©parer le domaine du bruit technique +```csharp +.WithExamples( + () => CurrencyMismatch( + new Amount(127.33m, Currency.EUR), + new Amount(84.10m, Currency.USD))) +``` -La documentation des erreurs doit se concentrer sur : +Les exemples sont du contenu pĂ©dagogique destinĂ© au catalogue, pas des tests de valeurs limites. PrĂ©fĂ©rez une ou deux occurrences reprĂ©sentatives Ă  des valeurs pathologiques. -* le sens mĂ©tier -* les rĂšgles violĂ©es -* les causes plausibles +Comme l’exemple appelle la vraie factory, la documentation gĂ©nĂ©rĂ©e reste Ă©galement reliĂ©e Ă  l’erreur rĂ©ellement produite Ă  l’exĂ©cution. -Évitez d’y faire apparaĂźtre : +## Exemple complet -* des stack traces -* des dĂ©tails du framework -* des noms de classes internes +```csharp +[ProvidesErrorsFor(nameof(Amount))] +public static class InvalidAmountOperationError { -## đŸ—‚ïž 10. Les trois messages que porte une erreur + [DocumentedBy(nameof(CurrencyMismatchDocumentation))] + internal static DomainError CurrencyMismatch(Amount left, Amount right) { + return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: $"Impossible de combiner des montants en {left.Currency} et {right.Currency} : {left} et {right}.") + .WithPublicMessage( + shortMessage: "Les montants utilisent des devises diffĂ©rentes.", + detailedMessage: "Tous les montants de cette opĂ©ration doivent utiliser la mĂȘme devise."); + } -Les Ă©lĂ©ments ci-dessus dĂ©crivent la **documentation** d’une erreur. En complĂ©ment, chaque factory dĂ©finit les trois messages que l’erreur porte Ă  l’exĂ©cution, via le builder Ă©tagĂ© `Type.Create(code, diagnosticMessage, 
).WithPublicMessage(shortMessage, detailedMessage)` : + private static ErrorDocumentation CurrencyMismatchDocumentation() { + return DescribeError + .WithTitle("IncohĂ©rence de devise des montants") + .WithDescription("Cette erreur survient lorsqu’une opĂ©ration combine des montants exprimĂ©s dans des devises diffĂ©rentes.") + .WithRule("Toutes les opĂ©rations monĂ©taires doivent impliquer des montants exprimĂ©s dans la mĂȘme devise.") + .WithDiagnostic( + "Des montants ont Ă©tĂ© utilisĂ©s avant d’avoir Ă©tĂ© convertis dans une devise commune.", + ErrorOrigin.Internal, + "VĂ©rifier que chaque montant a Ă©tĂ© converti dans la devise de l’opĂ©ration avant le calcul.") + .AndDiagnostic( + "Une requĂȘte externe a fourni des montants dans des devises incompatibles.", + ErrorOrigin.External, + "ContrĂŽler les devises fournies par l’appelant et celle attendue par l’opĂ©ration.") + .WithExamples(() => CurrencyMismatch( + new Amount(127.33m, Currency.EUR), + new Amount(84.10m, Currency.USD))); + } -| Message | Obligatoire | Public | Exposition | -| ------------------- | ----------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------- | -| `ShortMessage` | Oui | Utilisateurs finaux / API | RĂ©sumĂ© public sĂ»r (par ex. un `title` RFC 9457). | -| `DetailedMessage` | Non | Utilisateurs finaux / API | DĂ©tail public maĂźtrisĂ©, exposĂ© uniquement si l’application le dĂ©cide (par ex. un `detail` RFC 9457) ; jamais sensible ni interne. | -| `DiagnosticMessage` | Oui | Logs, support, dĂ©veloppeurs | DĂ©tail de diagnostic interne (identifiants, valeurs fautives, Ă©tat interne) ; jamais exposĂ© aux clients externes par dĂ©faut. | + private static class Code { + public static readonly ErrorCode CurrencyMismatch = + ErrorCode.Create("AMOUNT_CURRENCY_MISMATCH"); + } +} +``` -Gardez le message de diagnostic riche et technique ; gardez les messages publics sĂ»rs et exempts de dĂ©tail interne. `error.ToException()` utilise le message de diagnostic comme `Message` de l’exception. Lorsque l’erreur est exposĂ©e en HTTP, son `Code` est le `type` RFC 9457 naturel : exposez-le comme un URI stable tel que `urn:problem:{service}:{code}`, oĂč `{code}` est le code d’erreur en minuscules et en kebab-case — par exemple `urn:problem:temperature-simulator:temperature-below-absolute-zero` ou `urn:problem:banking-api:money-transfer-amount-not-positive` — afin que les clients puissent aiguiller sur le type de problĂšme (`type` et `status` restent l’affaire de l’application). +La documentation dĂ©crit la situation d’erreur stable. La factory crĂ©e sĂ©parĂ©ment une occurrence concrĂšte avec ses messages et ses valeurs d’exĂ©cution. -## 🏁 RĂ©sumĂ© +## Checklist de revue -Quand vous Ă©crivez une erreur : +Avant d’accepter une nouvelle documentation d’erreur, vĂ©rifiez que : -| ÉlĂ©ment | RĂŽle | -| --------------- | ----------------------------- | -| Code d’erreur | Identifiant stable | -| Message court | RĂ©sumĂ© public sĂ»r | -| Message dĂ©taillĂ© | DĂ©tail public maĂźtrisĂ© (optionnel) | -| Message de diagnostic | Interne, pour logs et support | -| Titre | RĂ©sumĂ© humain court | -| Description | Signification de l’erreur | -| RĂšgle | Invariant violĂ© | -| Cause | Pourquoi cela a pu arriver | -| Piste d'analyse | OĂč commencer l’investigation | -| Exemples | À quoi cela ressemble | +- la factory reprĂ©sente une situation prĂ©cise ; +- le code est spĂ©cifique, stable et Ă©crit en `UPPER_SNAKE_CASE` ; +- le titre nomme la situation au lieu d’annoncer un Ă©chec ; +- la description est comprĂ©hensible sans lire l’implĂ©mentation ; +- la rĂšgle est un vĂ©ritable invariant, ou est omise ; +- les causes de diagnostic sont des conditions plausibles et non des accusations ; +- les pistes d’analyse orientent l’investigation sans prescrire le workflow du support ; +- les exemples sont simples, rĂ©alistes et appellent la factory documentĂ©e ; +- aucun bruit technique ni donnĂ©e sensible n’apparaĂźt. -Des erreurs bien Ă©crites ne sont pas simplement levĂ©es. Elles deviennent une partie de la **comprĂ©hension partagĂ©e du fonctionnement — et des Ă©checs — du systĂšme.** +Pour choisir les textes publics et internes portĂ©s Ă  l’exĂ©cution, continuez avec [Écrire les messages d’une erreur](WritingErrorMessages.fr.md). Pour une liste de revue compacte Ă  l’échelle du projet, consultez [Bonnes pratiques](BestPractices.fr.md). ---
-← Guide du contexte d’erreur · ↑ Table des matiĂšres · Cas d’usage → +← Guide du contexte d’erreur · ↑ Table des matiĂšres · Écrire les messages d’une erreur →
--- \ No newline at end of file From 380ac3b151211ec190bc76b04eba9c46bc6c2bc5 Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 14:57:33 +0200 Subject: [PATCH 3/9] docs: add error message writing guide --- doc/WritingErrorMessages.en.md | 216 +++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 doc/WritingErrorMessages.en.md diff --git a/doc/WritingErrorMessages.en.md b/doc/WritingErrorMessages.en.md new file mode 100644 index 0000000..1daf538 --- /dev/null +++ b/doc/WritingErrorMessages.en.md @@ -0,0 +1,216 @@ +# Writing Error Messages + +🌍 **Languages:** +🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./WritingErrorMessages.fr.md) + +An error carries three runtime messages, but they serve only two audiences: + +- public messages for end users or API clients; +- an internal diagnostic message for logs, support, and developers. + +Keeping those audiences separate prevents internal information from leaking while preserving enough detail to investigate a concrete occurrence. + +## The three messages at a glance + +| Message | Required | Audience | Purpose | +| --- | --- | --- | --- | +| `ShortMessage` | yes | public | a safe, concise summary | +| `DetailedMessage` | no | public | an optional controlled explanation | +| `DiagnosticMessage` | yes | internal | concrete diagnostic information for this occurrence | + +They are created through the staged builder: + +```csharp +return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: $"Cannot combine {left.Currency} and {right.Currency} amounts: {left} and {right}.") + .WithPublicMessage( + shortMessage: "The amounts use different currencies.", + detailedMessage: "All amounts in this operation must use the same currency."); +``` + +The builder requires the internal message first and cannot produce an `Error` until the mandatory public short message is supplied. + +## `ShortMessage`: the safe public summary + +The short message tells the caller what happened without exposing implementation details. + +It should be: + +- safe to display directly; +- understandable without internal knowledge; +- concise enough for a UI notification or an RFC 9457 `title`; +- stable in meaning, although its wording may evolve or be localized. + +Good: + +> “The amounts use different currencies.” + +Avoid: + +> “Currency validation failed in `Amount.Add` for order 42.” + +The second example exposes implementation detail and an occurrence identifier that does not belong in a reusable public summary. + +## `DetailedMessage`: optional controlled public detail + +The detailed message gives the caller additional information when the application deliberately chooses to expose it. + +It may explain: + +- which public constraint was not satisfied; +- what kind of correction is expected from the caller; +- which safe input category caused rejection. + +Good: + +> “All amounts in this operation must use the same currency.” + +Avoid: + +> “The EUR amount was read from PostgreSQL while the USD amount came from provider X.” + +The detailed message remains public. It must not contain secrets, internal topology, stack traces, database details, private identifiers, or support-only instructions. + +Omit it when it would merely repeat the short message. + +## `DiagnosticMessage`: the internal occurrence detail + +The diagnostic message is intended for developers, support, and logs. It describes the concrete occurrence rather than the generic error definition. + +It may include useful internal facts such as: + +- identifiers needed for correlation; +- offending values, when safe for internal logs; +- dependency names; +- timeouts or response codes; +- expected and actual state. + +Good: + +> “Cannot combine EUR 127.33 and USD 84.10 in operation ORDER-7392.” + +Avoid messages that remain too generic: + +> “Currency mismatch.” + +The stable error documentation already says what a currency mismatch means. The diagnostic message should explain what was distinctive about this occurrence. + +`error.ToException()` uses `DiagnosticMessage` as the resulting exception’s `Message`. + +## Public does not mean harmless by default + +Before putting data in either public message, ask: + +1. Could this reveal personal, financial, security, or tenancy-specific information? +2. Could it expose an internal service, database, file path, class, or implementation choice? +3. Could the wording help an attacker distinguish internal states that should remain indistinguishable? +4. Would the same text still be appropriate in a client-visible API response? + +When in doubt, keep the detail in `DiagnosticMessage` or structured `ErrorContext`, subject to the application’s logging policy. + +## Messages and `ErrorContext` have different roles + +Do not turn the diagnostic message into an unstructured data dump. + +Use the message to provide a readable summary: + +```text +Order ORDER-7392 cannot be charged because the payment provider timed out. +``` + +Use typed context for fields that logs, dashboards, or tooling must query: + +```csharp +configureContext: ctx => ctx + .Add(ErrCtxKey.OrderId, orderId) + .Add(ErrCtxKey.Provider, providerName) +``` + +The two complement each other: + +- `DiagnosticMessage` helps a human read the event; +- `ErrorContext` helps humans and tools filter, correlate, and aggregate it. + +See [Error Context](ErrorContext.en.md) for key design and sensitivity guidance. + +## HTTP and RFC 9457 + +The core error model is HTTP-agnostic. An application may map: + +| FirstClassErrors value | Typical RFC 9457 field | +| --- | --- | +| stable `Code` represented as a URI | `type` | +| `ShortMessage` | `title` | +| `DetailedMessage`, when explicitly exposed | `detail` | + +For example: + +```json +{ + "type": "urn:problem:billing-api:amount-currency-mismatch", + "title": "The amounts use different currencies.", + "detail": "All amounts in this operation must use the same currency.", + "status": 422 +} +``` + +`DiagnosticMessage` is not a default response field. The application remains responsible for choosing `status`, deciding whether to expose `detail`, and enforcing its security policy. + +## Localization + +Public messages are suitable for localization because they address the caller. Read `ShortMessage` and `DetailedMessage` from resources under the selected UI culture when needed. + +Keep `DiagnosticMessage` in the team’s invariant authoring language. A consistent internal language makes logs and support investigations easier to search and correlate across callers. + +See [Internationalization](Internationalization.en.md) for the extraction and rendering flow. + +## Bad and improved example + +Too little separation: + +```csharp +return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: "Invalid operation.") + .WithPublicMessage( + shortMessage: $"Order {orderId}: database currencies {left.Currency}/{right.Currency} do not match.", + detailedMessage: "Contact the Payments team with the SQL trace."); +``` + +Improved: + +```csharp +return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: $"Order {orderId} cannot combine {left.Currency} and {right.Currency} amounts: {left} and {right}.", + configureContext: ctx => ctx.Add(ErrCtxKey.OrderId, orderId)) + .WithPublicMessage( + shortMessage: "The amounts use different currencies.", + detailedMessage: "All amounts in this operation must use the same currency."); +``` + +The improved version gives the caller safe information, gives support occurrence-specific detail, and keeps queryable data structured. + +## Review checklist + +Verify that: + +- the short message is concise, public, and free of implementation detail; +- the detailed message adds useful public information rather than repeating the summary; +- neither public message contains sensitive or support-only data; +- the diagnostic message explains the concrete occurrence; +- queryable values are also captured as structured context where appropriate; +- the diagnostic message is never exposed externally by default; +- public messages are localized when the application supports multiple caller languages; +- internal diagnostic wording remains consistent across cultures. + +For the stable title, description, rule, diagnostics, and examples, return to [Writing Error Documentation](WritingErrorsGuide.en.md). + +--- + +
+← Writing Error Documentation · ↑ Table of contents · Usage Patterns → +
+ +--- \ No newline at end of file From 23d6938aa1473e7cabf86615fe26ae98107d07f6 Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 14:58:36 +0200 Subject: [PATCH 4/9] docs: add error message writing guide --- doc/WritingErrorMessages.fr.md | 216 +++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 doc/WritingErrorMessages.fr.md diff --git a/doc/WritingErrorMessages.fr.md b/doc/WritingErrorMessages.fr.md new file mode 100644 index 0000000..4a53f40 --- /dev/null +++ b/doc/WritingErrorMessages.fr.md @@ -0,0 +1,216 @@ +# Écrire les messages d’une erreur + +🌍 **Langues :** +🇬🇧 [English](./WritingErrorMessages.en.md) | đŸ‡«đŸ‡· Français (ce fichier) + +Une erreur porte trois messages Ă  l’exĂ©cution, mais ils ne s’adressent qu’à deux publics : + +- des messages publics pour les utilisateurs finaux ou les clients d’API ; +- un message de diagnostic interne pour les logs, le support et les dĂ©veloppeurs. + +SĂ©parer ces publics empĂȘche les fuites d’informations internes tout en conservant assez de dĂ©tail pour analyser une occurrence concrĂšte. + +## Les trois messages en un coup d’Ɠil + +| Message | Obligatoire | Public | RĂŽle | +| --- | --- | --- | --- | +| `ShortMessage` | oui | externe | un rĂ©sumĂ© court et sĂ»r | +| `DetailedMessage` | non | externe | une explication optionnelle et maĂźtrisĂ©e | +| `DiagnosticMessage` | oui | interne | les informations de diagnostic propres Ă  cette occurrence | + +Ils sont créés avec le builder Ă©tagĂ© : + +```csharp +return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: $"Impossible de combiner des montants en {left.Currency} et {right.Currency} : {left} et {right}.") + .WithPublicMessage( + shortMessage: "Les montants utilisent des devises diffĂ©rentes.", + detailedMessage: "Tous les montants de cette opĂ©ration doivent utiliser la mĂȘme devise."); +``` + +Le builder demande d’abord le message interne et ne peut produire une `Error` qu’aprĂšs la fourniture du message public court obligatoire. + +## `ShortMessage` : le rĂ©sumĂ© public sĂ»r + +Le message court indique Ă  l’appelant ce qui s’est passĂ© sans exposer de dĂ©tail d’implĂ©mentation. + +Il doit ĂȘtre : + +- affichable directement sans risque ; +- comprĂ©hensible sans connaissance interne ; +- assez concis pour une notification UI ou le `title` d’un problem detail RFC 9457 ; +- stable dans son sens, mĂȘme si sa formulation Ă©volue ou est localisĂ©e. + +Bon : + +> « Les montants utilisent des devises diffĂ©rentes. » + +À Ă©viter : + +> « La validation des devises a Ă©chouĂ© dans `Amount.Add` pour la commande 42. » + +Le second exemple expose un dĂ©tail d’implĂ©mentation et un identifiant d’occurrence qui n’ont pas leur place dans un rĂ©sumĂ© public rĂ©utilisable. + +## `DetailedMessage` : le dĂ©tail public optionnel + +Le message dĂ©taillĂ© apporte une information supplĂ©mentaire lorsque l’application choisit explicitement de l’exposer. + +Il peut expliquer : + +- quelle contrainte publique n’a pas Ă©tĂ© respectĂ©e ; +- quelle correction est attendue de l’appelant ; +- quelle catĂ©gorie d’entrĂ©e sĂ»re a provoquĂ© le rejet. + +Bon : + +> « Tous les montants de cette opĂ©ration doivent utiliser la mĂȘme devise. » + +À Ă©viter : + +> « Le montant en EUR provient de PostgreSQL tandis que le montant en USD vient du fournisseur X. » + +Le message dĂ©taillĂ© reste public. Il ne doit contenir ni secret, ni topologie interne, ni stack trace, ni dĂ©tail de base de donnĂ©es, ni identifiant privĂ©, ni instruction rĂ©servĂ©e au support. + +Omettez-le lorsqu’il ne ferait que rĂ©pĂ©ter le message court. + +## `DiagnosticMessage` : le dĂ©tail interne de l’occurrence + +Le message de diagnostic est destinĂ© aux dĂ©veloppeurs, au support et aux logs. Il dĂ©crit l’occurrence concrĂšte plutĂŽt que la dĂ©finition gĂ©nĂ©rale de l’erreur. + +Il peut contenir des faits internes utiles comme : + +- des identifiants nĂ©cessaires Ă  la corrĂ©lation ; +- les valeurs fautives, lorsqu’elles sont sĂ»res pour les logs internes ; +- le nom d’une dĂ©pendance ; +- un timeout ou un code de rĂ©ponse ; +- l’état attendu et l’état constatĂ©. + +Bon : + +> « Impossible de combiner 127,33 EUR et 84,10 USD dans l’opĂ©ration ORDER-7392. » + +Évitez les messages trop gĂ©nĂ©riques : + +> « IncohĂ©rence de devise. » + +La documentation stable explique dĂ©jĂ  ce que signifie une incohĂ©rence de devise. Le message de diagnostic doit montrer ce qui distingue cette occurrence. + +`error.ToException()` utilise `DiagnosticMessage` comme `Message` de l’exception produite. + +## Public ne signifie pas automatiquement sans risque + +Avant de placer une donnĂ©e dans un message public, demandez-vous : + +1. Peut-elle rĂ©vĂ©ler une information personnelle, financiĂšre, de sĂ©curitĂ© ou propre Ă  un tenant ? +2. Peut-elle exposer un service interne, une base de donnĂ©es, un chemin de fichier, une classe ou un choix d’implĂ©mentation ? +3. La formulation aide-t-elle un attaquant Ă  distinguer des Ă©tats internes qui devraient rester indiffĂ©renciĂ©s ? +4. Le mĂȘme texte serait-il toujours appropriĂ© dans une rĂ©ponse API visible par un client ? + +En cas de doute, gardez le dĂ©tail dans `DiagnosticMessage` ou dans un `ErrorContext` structurĂ©, sous rĂ©serve de la politique de logs de l’application. + +## Les messages et `ErrorContext` ont des rĂŽles diffĂ©rents + +Ne transformez pas le message de diagnostic en dump de donnĂ©es non structurĂ©. + +Utilisez le message pour fournir un rĂ©sumĂ© lisible : + +```text +La commande ORDER-7392 ne peut pas ĂȘtre dĂ©bitĂ©e car le fournisseur de paiement a dĂ©passĂ© le dĂ©lai. +``` + +Utilisez le contexte typĂ© pour les champs que les logs, dashboards ou outils doivent interroger : + +```csharp +configureContext: ctx => ctx + .Add(ErrCtxKey.OrderId, orderId) + .Add(ErrCtxKey.Provider, providerName) +``` + +Les deux se complĂštent : + +- `DiagnosticMessage` aide une personne Ă  lire l’évĂ©nement ; +- `ErrorContext` aide les personnes et les outils Ă  filtrer, corrĂ©ler et agrĂ©ger. + +Voir [Contexte d’erreur](ErrorContext.fr.md) pour la conception des clĂ©s et les rĂšgles liĂ©es aux donnĂ©es sensibles. + +## HTTP et RFC 9457 + +Le modĂšle d’erreur du cƓur est agnostique vis-Ă -vis d’HTTP. Une application peut mapper : + +| Valeur FirstClassErrors | Champ RFC 9457 courant | +| --- | --- | +| `Code` stable reprĂ©sentĂ© comme URI | `type` | +| `ShortMessage` | `title` | +| `DetailedMessage`, lorsqu’il est explicitement exposĂ© | `detail` | + +Exemple : + +```json +{ + "type": "urn:problem:billing-api:amount-currency-mismatch", + "title": "Les montants utilisent des devises diffĂ©rentes.", + "detail": "Tous les montants de cette opĂ©ration doivent utiliser la mĂȘme devise.", + "status": 422 +} +``` + +`DiagnosticMessage` n’est pas un champ de rĂ©ponse par dĂ©faut. L’application reste responsable du choix de `status`, de la dĂ©cision d’exposer ou non `detail` et de l’application de sa politique de sĂ©curitĂ©. + +## Localisation + +Les messages publics peuvent ĂȘtre localisĂ©s puisqu’ils s’adressent Ă  l’appelant. Lisez `ShortMessage` et `DetailedMessage` depuis des ressources selon la culture UI sĂ©lectionnĂ©e lorsque nĂ©cessaire. + +Conservez `DiagnosticMessage` dans la langue de travail interne de l’équipe. Une langue interne cohĂ©rente facilite la recherche et la corrĂ©lation dans les logs et les investigations du support, quelle que soit la langue de l’appelant. + +Voir [Internationalisation](Internationalisation.fr.md) pour le flux d’extraction et de rendu. + +## Exemple incorrect puis amĂ©liorĂ© + +SĂ©paration insuffisante : + +```csharp +return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: "OpĂ©ration invalide.") + .WithPublicMessage( + shortMessage: $"Commande {orderId} : les devises SQL {left.Currency}/{right.Currency} diffĂšrent.", + detailedMessage: "Contacter l’équipe Paiement avec la trace SQL."); +``` + +Version amĂ©liorĂ©e : + +```csharp +return DomainError.Create( + Code.CurrencyMismatch, + diagnosticMessage: $"La commande {orderId} ne peut pas combiner des montants en {left.Currency} et {right.Currency} : {left} et {right}.", + configureContext: ctx => ctx.Add(ErrCtxKey.OrderId, orderId)) + .WithPublicMessage( + shortMessage: "Les montants utilisent des devises diffĂ©rentes.", + detailedMessage: "Tous les montants de cette opĂ©ration doivent utiliser la mĂȘme devise."); +``` + +La version amĂ©liorĂ©e fournit une information sĂ»re Ă  l’appelant, un dĂ©tail propre Ă  l’occurrence au support et des donnĂ©es interrogeables sous forme structurĂ©e. + +## Checklist de revue + +VĂ©rifiez que : + +- le message court est concis, public et sans dĂ©tail d’implĂ©mentation ; +- le message dĂ©taillĂ© ajoute une information publique utile au lieu de rĂ©pĂ©ter le rĂ©sumĂ© ; +- aucun message public ne contient de donnĂ©e sensible ou rĂ©servĂ©e au support ; +- le message de diagnostic explique l’occurrence concrĂšte ; +- les valeurs interrogeables sont Ă©galement placĂ©es dans le contexte structurĂ© lorsque pertinent ; +- le message de diagnostic n’est jamais exposĂ© Ă  l’extĂ©rieur par dĂ©faut ; +- les messages publics sont localisĂ©s lorsque l’application prend en charge plusieurs langues ; +- la formulation interne reste cohĂ©rente entre les cultures. + +Pour le titre, la description, la rĂšgle, les diagnostics et les exemples stables, revenez Ă  [Écrire la documentation d’une erreur](WritingErrorsGuide.fr.md). + +--- + +
+← Écrire la documentation d’une erreur · ↑ Table des matiĂšres · Cas d’usage → +
+ +--- \ No newline at end of file From 4682cdb20902c3324d20b2fbd86a0052866be0e7 Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 14:59:05 +0200 Subject: [PATCH 5/9] docs: turn best practices into a review checklist --- doc/BestPractices.en.md | 229 ++++++++++++---------------------------- 1 file changed, 66 insertions(+), 163 deletions(-) diff --git a/doc/BestPractices.en.md b/doc/BestPractices.en.md index dbf6709..05a6c99 100644 --- a/doc/BestPractices.en.md +++ b/doc/BestPractices.en.md @@ -3,207 +3,110 @@ 🌍 **Languages:** 🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./BestPractices.fr.md) -FirstClassErrors is most effective when used consistently and with intention. -These practices help keep errors meaningful, readable, and useful. +Use this page as a compact review checklist. Detailed explanations belong in the focused guides linked from each section. -## 🧠 1. One error situation per factory +## Model the right situation -Each factory method should represent **one precise error situation**. +- One factory represents one precise error situation. +- Avoid generic catch-all errors such as `INVALID_OPERATION` or `PROCESSING_FAILED`. +- Choose the error type from the meaning of the failure, not from the current class or folder. +- Do not document framework exceptions, accidental crashes, or low-level technical noise as application knowledge. -Avoid: +See [Writing Error Documentation](WritingErrorsGuide.en.md) and [When Not to Use FirstClassErrors](WhenNotToUseFirstClassErrors.en.md). -* factories that cover multiple different causes -* generic “InvalidOperation” factories +## Keep identifiers stable -A factory should answer: +- Use a specific `UPPER_SNAKE_CASE` error code. +- Never reuse a code for a different situation. +- Do not rename or remove a code casually. +- Keep context-key names and value types stable when dashboards or consumers depend on them. -> “What exactly went wrong?” +Codes and context shape form an operational contract. Use [Catalog Versioning](CatalogVersioning.en.md) to make contract changes visible. -**Why:** -Clear boundaries between error situations make diagnostics precise and documentation reliable. - -## đŸ·ïž 2. Keep error codes stable - -Error codes are part of the contract. - -* Do not change codes casually -* Do not reuse a code for another situation -* Treat them as long-lived identifiers - -**Why:** -Error codes are used in logs, documentation, and support workflows. Stability preserves traceability over time. - -## ✂ 3. Keep the happy path clean - -Error factories should keep error construction out of domain logic. +## Centralize construction in factories Prefer: ```csharp -throw InvalidAmountOperationError.CurrencyMismatch(a1, a2).ToException(); +throw InvalidAmountOperationError.CurrencyMismatch(left, right).ToException(); ``` -Over: - -```csharp -throw new DomainException(/* manually assembled Error */); -``` - -**Why:** -This keeps business logic readable and separates domain intent from error construction details. - -## 📘 4. Write documentation for humans - -Error documentation is not for the compiler — it is for: - -* developers -* support -* operators - -Avoid technical noise. Focus on: - -* meaning -* rule -* plausible causes - -## 🔎 5. Diagnostics are hypotheses, not blame - -Diagnostics should describe possible states, not accuse actors. - -Prefer: - -> “Amounts were used without conversion.” - -Avoid: - -> “The developer forgot to convert.” - -**Why:** -Diagnostics guide investigation. Blame-oriented wording harms collaboration and does not help troubleshooting. +Do not assemble errors or exceptions inline in business logic. -## 🧭 6. Analysis leads guide, they don’t prescribe +A dedicated static factory class annotated with `[ProvidesErrorsFor(...)]` should group related situations, with one factory method per situation and its documentation nearby. -Do not include operational processes or support procedures. +This keeps the happy path readable and gives every occurrence the same code, messages, context, and documentation anchor. -Avoid: +## Separate stable documentation from runtime messages -* “Open a ticket” -* “Contact team X” +Stable documentation explains the error category: -Focus on investigation direction, not workflow. +- title; +- description; +- violated rule; +- diagnostic hypotheses; +- representative examples. -**Why:** -Operational processes depend on organizational context, not on the application itself. Encoding them in error documentation couples your code to external procedures and makes documentation brittle when processes change. +Runtime messages explain one occurrence: -## 🔁 7. Use Outcome where failure is expected +- `ShortMessage`: safe public summary; +- `DetailedMessage`: optional controlled public detail; +- `DiagnosticMessage`: internal diagnostic detail. -Use exceptions for: +Do not put occurrence-specific identifiers into stable documentation, and do not expose internal diagnostic detail through public messages. -* invariant violations -* unexpected states +See [Writing Error Documentation](WritingErrorsGuide.en.md) and [Writing Error Messages](WritingErrorMessages.en.md). -Use `Outcome` when: +## Write for investigation, not blame -* validating input -* processing batches -* partial failure is normal +- Causes describe plausible states or conditions. +- `ErrorOrigin` classifies where a cause may lie; it does not assign responsibility. +- Analysis leads start with neutral guidance such as *Check*, *Verify*, or *Review*. +- Do not encode ticketing, escalation, or team-contact procedures in error documentation. -**Why:** -This keeps exception flow meaningful while still allowing rich error information in non-exceptional scenarios. +Operational processes change independently from application behavior. -## đŸ§© 8. Don’t document technical accidents +## Keep context useful and safe -Avoid documenting: +- Add context at factory level so every occurrence is consistent. +- Use named, typed, reusable keys. +- Include instance-level facts that materially improve diagnosis. +- Avoid secrets, oversized payloads, and data that cannot be logged safely. +- Prefer structured context to embedding every value inside a message. -* NullReferenceExceptions -* framework exceptions -* low-level technical failures +See [Error Context](ErrorContext.en.md). -The DSL is for **meaningful application errors**, not incidental crashes. +## Choose exceptions or `Outcome` intentionally -**Why:** -The goal is to document system behavior and rules, not unpredictable technical incidents. +Use an exception when the failure should interrupt the current operation, such as an invariant violation or an unrecoverable state at that level. -## đŸ§Ș 9. Examples should educate, not stress test +Use `Outcome` / `Outcome` when failure is an expected branch of the flow, such as validation, parsing, batch processing, or partial success. -Examples are not unit tests. +Both paths should carry the same `Error` created by the same factory. Do not create a second, weaker error model for non-throwing flows. -Use: - -* simple -* realistic -* clear values - -Avoid edge cases or pathological data. - -## đŸ§± 10. Keep documentation close to the factory - -Documentation methods should live in the same error factory class as the factory. - -This keeps: - -* intent -* error creation -* documentation - -in the same conceptual place. - -**Why:** -Keeping documentation next to the factory ensures it evolves with the code. This prevents drift and preserves the core idea of living documentation: knowledge stays where the behavior is defined. - -## đŸ§© 11. Group errors in a dedicated factory class - -Application-specific errors should be grouped in a `static` factory class annotated with `[ProvidesErrorsFor(...)]`, with one `internal static` factory method per error situation. - -```csharp -[ProvidesErrorsFor(nameof(Amount))] -public static class InvalidAmountOperationError { - - [DocumentedBy(nameof(CurrencyMismatchDocumentation))] - internal static DomainError CurrencyMismatch(Amount left, Amount right) { - return DomainError.Create( - Code.CurrencyMismatch, - diagnosticMessage: $"Cannot operate on amounts with different currencies: {left.Currency} and {right.Currency}.") - .WithPublicMessage( - shortMessage: "Amounts use different currencies.", - detailedMessage: "The amounts involved use different currencies."); - } - - // ... documentation method and error codes ... -} -``` - -**Why:** -Each factory method represents a well-defined error category. Grouping them in a dedicated class keeps related error situations, their codes, and their documentation in one place. Note that the core types (`DomainError`, `DomainException`, 
) are **not** sealed — inheritance is intentionally allowed so you can model your own error hierarchies — but in practice you author error situations through these factory classes rather than by subclassing. - -## 🏭 12. Build errors through factories, throw via `ToException()` - -You never `new` a `DiagnosableException` in user code: an exception's only constructor takes an `Error`. Errors themselves are no longer built through public constructors either — those are now internal. An error is assembled through the staged builder — `Type.Create(code, diagnosticMessage, 
)` captures the mandatory internal information and returns an intermediate stage, and `.WithPublicMessage(shortMessage, detailedMessage)` finalizes the real error (there is no `.Build()`). Factory methods encapsulate that call, so you simply invoke the factory and turn its result into an exception with `ToException()`. - -```csharp -// Build an Error through the factory, then throw it as an exception: -throw InvalidAmountOperationError.CurrencyMismatch(a1, a2).ToException(); -``` - -When failure is expected rather than exceptional, return the same factory's `Error` inside an `Outcome`: - -```csharp -return Outcome.Failure(InvalidAmountOperationError.NegativeAmount(value)); -``` +See [Usage Patterns](UsagePatterns.en.md). -**Why:** -Routing every error through a factory ensures that all errors of a given category are created in a controlled, documented, and semantically consistent way, whether they are thrown as exceptions or carried as `Outcome` failures. +## Make examples educational -## 🎯 Final thought +- Use simple, realistic values. +- Make the violated rule immediately visible. +- Call the documented factory rather than copying a message. +- Keep boundary and stress cases in tests, not in catalog examples. -FirstClassErrors is about **expressing knowledge**, not just handling errors. +## Pull-request checklist -Well-written errors improve: +Before merging an error-related change, verify that: -* code readability -* troubleshooting -* documentation -* shared understanding of the system +- [ ] each new factory represents one precise situation; +- [ ] every code is specific, stable, and unique; +- [ ] documentation is linked with `[DocumentedBy]`; +- [ ] public messages contain no internal or sensitive information; +- [ ] diagnostic messages explain concrete occurrences; +- [ ] queryable occurrence data uses typed context where appropriate; +- [ ] diagnostics are hypotheses and analysis leads are actionable; +- [ ] examples are realistic and call the real factory; +- [ ] English and French documentation remain aligned when both are changed; +- [ ] catalog baseline changes are deliberate and reviewed when the contract changes. --- From c6ec57fb2f0e21827bc5017bd2cf87d8c45f2173 Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 14:59:52 +0200 Subject: [PATCH 6/9] docs: turn best practices into a review checklist --- doc/BestPractices.fr.md | 231 ++++++++++++---------------------------- 1 file changed, 67 insertions(+), 164 deletions(-) diff --git a/doc/BestPractices.fr.md b/doc/BestPractices.fr.md index bff6cc2..f65b634 100644 --- a/doc/BestPractices.fr.md +++ b/doc/BestPractices.fr.md @@ -1,209 +1,112 @@ # Bonnes pratiques -🌍 **Langues:** +🌍 **Langues :** 🇬🇧 [English](./BestPractices.en.md) | đŸ‡«đŸ‡· Français (ce fichier) -FirstClassErrors est le plus efficace lorsqu’il est utilisĂ© de maniĂšre cohĂ©rente et intentionnelle. -Ces pratiques aident Ă  garder des erreurs significatives, lisibles et rĂ©ellement utiles. +Utilisez cette page comme une checklist de revue compacte. Les explications dĂ©taillĂ©es se trouvent dans les guides spĂ©cialisĂ©s liĂ©s depuis chaque section. -## 🧠 1. Une situation d’erreur par factory +## ModĂ©liser la bonne situation -Chaque mĂ©thode factory doit reprĂ©senter **une situation d’erreur prĂ©cise**. +- Une factory reprĂ©sente une situation d’erreur prĂ©cise. +- Évitez les erreurs gĂ©nĂ©riques comme `INVALID_OPERATION` ou `PROCESSING_FAILED`. +- Choisissez le type d’erreur selon le sens de l’échec, pas selon la classe ou le dossier courant. +- Ne documentez pas les exceptions du framework, les crashes accidentels ou le bruit technique bas niveau comme de la connaissance applicative. -Évitez : +Voir [Écrire la documentation d’une erreur](WritingErrorsGuide.fr.md) et [Quand ne pas utiliser FirstClassErrors](WhenNotToUseFirstClassErrors.fr.md). -* les factories qui couvrent plusieurs causes diffĂ©rentes -* les factories gĂ©nĂ©riques de type “InvalidOperation” +## Garder les identifiants stables -Une factory doit rĂ©pondre Ă  : +- Utilisez un code spĂ©cifique en `UPPER_SNAKE_CASE`. +- Ne rĂ©utilisez jamais un code pour une autre situation. +- Ne renommez ou ne supprimez pas un code Ă  la lĂ©gĂšre. +- Gardez stables les noms et types des clĂ©s de contexte lorsque des dashboards ou consommateurs en dĂ©pendent. -> « Qu’est-ce qui s’est exactement mal passĂ© ? » +Les codes et la forme du contexte constituent un contrat opĂ©rationnel. Utilisez le [Versionnage du catalogue](CatalogVersioning.fr.md) pour rendre ses modifications visibles. -**Pourquoi :** -Des frontiĂšres claires entre les situations d’erreur rendent les diagnostics prĂ©cis et la documentation fiable. - -## đŸ·ïž 2. Garder les codes d’erreur stables - -Les codes d’erreur font partie du contrat. - -* Ne changez pas les codes Ă  la lĂ©gĂšre -* Ne rĂ©utilisez pas un code pour une autre situation -* Traitez-les comme des identifiants durables - -**Pourquoi :** -Les codes d’erreur sont utilisĂ©s dans les logs, la documentation et les processus de support. Leur stabilitĂ© prĂ©serve la traçabilitĂ© dans le temps. - -## ✂ 3. Garder le happy path propre - -Les factories d’erreur doivent Ă©viter d’introduire la construction d’erreur directement dans la logique mĂ©tier. +## Centraliser la construction dans des factories PrĂ©fĂ©rez : ```csharp -throw InvalidAmountOperationError.CurrencyMismatch(a1, a2).ToException(); -```` - -PlutĂŽt que : - -```csharp -throw new DomainException(/* Error assemblĂ©e manuellement */); +throw InvalidAmountOperationError.CurrencyMismatch(left, right).ToException(); ``` -**Pourquoi :** -Cela garde la logique mĂ©tier lisible et sĂ©pare l’intention mĂ©tier des dĂ©tails de construction de l’erreur. - -## 📘 4. Écrire la documentation pour des humains - -La documentation des erreurs n’est pas destinĂ©e au compilateur — elle est destinĂ©e : - -* aux dĂ©veloppeurs -* au support -* aux opĂ©rateurs - -Évitez le bruit technique. Concentrez-vous sur : - -* le sens -* la rĂšgle -* les causes plausibles - -## 🔎 5. Les diagnostics sont des hypothĂšses, pas des accusations - -Les diagnostics doivent dĂ©crire des Ă©tats possibles, pas accuser des acteurs. - -PrĂ©fĂ©rez : - -> « Des montants ont Ă©tĂ© utilisĂ©s sans conversion. » - -Évitez : - -> « Le dĂ©veloppeur a oubliĂ© de convertir. » - -**Pourquoi :** -Les diagnostics guident l’investigation. Un langage accusateur nuit Ă  la collaboration et n’aide pas au dĂ©pannage. - -## 🧭 6. Les pistes d’analyse guident, elles ne prescrivent pas +N’assemblez pas les erreurs ou exceptions directement dans la logique mĂ©tier. -N’incluez pas de processus opĂ©rationnels ou de procĂ©dures de support. +Une classe factory statique annotĂ©e avec `[ProvidesErrorsFor(...)]` doit regrouper les situations liĂ©es, avec une mĂ©thode par situation et sa documentation Ă  proximitĂ©. -Évitez : +Cela garde le happy path lisible et garantit Ă  chaque occurrence les mĂȘmes code, messages, contexte et point d’ancrage documentaire. -* « Ouvrir un ticket » -* « Contacter l’équipe X » +## SĂ©parer documentation stable et messages runtime -Concentrez-vous sur la direction de l’investigation, pas sur le workflow. +La documentation stable explique la catĂ©gorie d’erreur : -**Pourquoi :** -Les processus opĂ©rationnels dĂ©pendent du contexte organisationnel, pas de l’application elle-mĂȘme. Les encoder dans la documentation des erreurs couple votre code Ă  des procĂ©dures externes et rend la documentation fragile lorsque ces processus changent. +- titre ; +- description ; +- rĂšgle violĂ©e ; +- hypothĂšses de diagnostic ; +- exemples reprĂ©sentatifs. -## 🔁 7. Utiliser Outcome quand l’échec est attendu +Les messages runtime expliquent une occurrence : -Utilisez des exceptions pour : +- `ShortMessage` : rĂ©sumĂ© public sĂ»r ; +- `DetailedMessage` : dĂ©tail public optionnel et maĂźtrisĂ© ; +- `DiagnosticMessage` : dĂ©tail de diagnostic interne. -* les violations d’invariants -* les Ă©tats inattendus +Ne placez pas d’identifiants propres Ă  une occurrence dans la documentation stable et n’exposez pas de dĂ©tail interne dans les messages publics. -Utilisez `Outcome` lorsque : +Voir [Écrire la documentation d’une erreur](WritingErrorsGuide.fr.md) et [Écrire les messages d’une erreur](WritingErrorMessages.fr.md). -* vous validez des entrĂ©es -* vous traitez des lots -* les Ă©checs partiels sont normaux +## Écrire pour l’investigation, pas pour accuser -**Pourquoi :** -Cela maintient le flux d’exceptions significatif tout en permettant de transmettre des informations d’erreur riches dans des scĂ©narios non exceptionnels. +- Les causes dĂ©crivent des Ă©tats ou conditions plausibles. +- `ErrorOrigin` classe l’endroit oĂč une cause peut se situer ; il n’attribue pas une responsabilitĂ©. +- Les pistes d’analyse commencent par des verbes neutres comme *VĂ©rifier*, *ContrĂŽler* ou *Examiner*. +- N’encodez pas les procĂ©dures de ticketing, d’escalade ou de contact d’équipe dans la documentation. -## đŸ§© 8. Ne pas documenter les accidents techniques +Les processus opĂ©rationnels Ă©voluent indĂ©pendamment du comportement applicatif. -Évitez de documenter : +## Garder le contexte utile et sĂ»r -* les NullReferenceExceptions -* les exceptions du framework -* les dĂ©faillances techniques bas niveau +- Ajoutez le contexte au niveau de la factory afin que chaque occurrence soit cohĂ©rente. +- Utilisez des clĂ©s nommĂ©es, typĂ©es et rĂ©utilisables. +- Incluez les faits propres Ă  l’occurrence qui amĂ©liorent rĂ©ellement le diagnostic. +- Évitez les secrets, les payloads volumineux et les donnĂ©es qui ne peuvent pas ĂȘtre loguĂ©es sans risque. +- PrĂ©fĂ©rez un contexte structurĂ© Ă  l’insertion de toutes les valeurs dans un message. -Le DSL est destinĂ© aux **erreurs applicatives porteuses de sens**, pas aux crashes accidentels. +Voir [Contexte d’erreur](ErrorContext.fr.md). -**Pourquoi :** -L’objectif est de documenter le comportement et les rĂšgles du systĂšme, pas des incidents techniques imprĂ©visibles. +## Choisir intentionnellement exception ou `Outcome` -## đŸ§Ș 9. Les exemples doivent Ă©duquer, pas tester les limites +Utilisez une exception lorsque l’échec doit interrompre l’opĂ©ration courante, par exemple pour une violation d’invariant ou un Ă©tat irrĂ©cupĂ©rable Ă  ce niveau. -Les exemples ne sont pas des tests unitaires. +Utilisez `Outcome` / `Outcome` lorsque l’échec est une branche attendue du flux, par exemple pour la validation, le parsing, les traitements par lots ou les succĂšs partiels. -Utilisez des valeurs : +Les deux chemins doivent porter la mĂȘme `Error`, créée par la mĂȘme factory. Ne crĂ©ez pas un second modĂšle d’erreur appauvri pour les flux sans exception. -* simples -* rĂ©alistes -* claires - -Évitez les cas extrĂȘmes ou les donnĂ©es pathologiques. - -## đŸ§± 10. Garder la documentation proche de la factory - -Les mĂ©thodes de documentation doivent vivre dans la mĂȘme classe factory d’erreur que la factory. - -Cela garde : - -* l’intention -* la crĂ©ation de l’erreur -* la documentation - -au mĂȘme endroit conceptuel. - -**Pourquoi :** -Garder la documentation Ă  cĂŽtĂ© de la factory garantit qu’elle Ă©volue avec le code. Cela Ă©vite les dĂ©rives et prĂ©serve l’idĂ©e centrale de documentation vivante : la connaissance reste lĂ  oĂč le comportement est dĂ©fini. - -## đŸ§© 11. Regrouper les erreurs dans une classe factory dĂ©diĂ©e - -Les erreurs spĂ©cifiques Ă  l’application devraient ĂȘtre regroupĂ©es dans une classe `static` annotĂ©e avec `[ProvidesErrorsFor(...)]`, avec une mĂ©thode factory `internal static` par situation d’erreur. - -```csharp -[ProvidesErrorsFor(nameof(Amount))] -public static class InvalidAmountOperationError { - - [DocumentedBy(nameof(CurrencyMismatchDocumentation))] - internal static DomainError CurrencyMismatch(Amount left, Amount right) { - return DomainError.Create( - Code.CurrencyMismatch, - diagnosticMessage: $"Impossible d’opĂ©rer sur des montants de devises diffĂ©rentes : {left.Currency} et {right.Currency}.") - .WithPublicMessage( - shortMessage: "Les montants utilisent des devises diffĂ©rentes.", - detailedMessage: "Les montants impliquĂ©s utilisent des devises diffĂ©rentes."); - } - - // ... mĂ©thode de documentation et codes d’erreur ... -} -``` - -**Pourquoi :** -Chaque mĂ©thode factory reprĂ©sente une catĂ©gorie d’erreur bien dĂ©finie. Les regrouper dans une classe dĂ©diĂ©e garde au mĂȘme endroit les situations d’erreur liĂ©es, leurs codes et leur documentation. Notez que les types du cƓur (`DomainError`, `DomainException`, 
) ne sont **pas** `sealed` — l’hĂ©ritage est intentionnellement autorisĂ© afin de pouvoir modĂ©liser vos propres hiĂ©rarchies d’erreur — mais en pratique vous dĂ©crivez les situations d’erreur via ces classes factory plutĂŽt qu’en dĂ©rivant des sous-classes. - -## 🏭 12. Construire les erreurs via des factories, lever avec `ToException()` - -Vous ne faites jamais `new` sur une `DiagnosableException` dans votre code : le seul constructeur d’une exception prend une `Error`. Les erreurs elles-mĂȘmes ne se construisent plus non plus via des constructeurs publics — ceux-ci sont dĂ©sormais internes. Une erreur s’assemble via le builder Ă©tagĂ© — `Type.Create(code, diagnosticMessage, 
)` capture l’information interne obligatoire et retourne une Ă©tape intermĂ©diaire, et `.WithPublicMessage(shortMessage, detailedMessage)` finalise l’erreur rĂ©elle (il n’y a pas de `.Build()`). Les mĂ©thodes factory encapsulent cet appel : vous invoquez simplement la factory et transformez son rĂ©sultat en exception avec `ToException()`. - -```csharp -// Construit une Error via la factory, puis la lĂšve en tant qu’exception : -throw InvalidAmountOperationError.CurrencyMismatch(a1, a2).ToException(); -``` - -Lorsque l’échec est attendu plutĂŽt qu’exceptionnel, retournez l’`Error` de la mĂȘme factory dans un `Outcome` : - -```csharp -return Outcome.Failure(InvalidAmountOperationError.NegativeAmount(value)); -``` +Voir [Cas d’usage](UsagePatterns.fr.md). -**Pourquoi :** -Faire passer chaque erreur par une factory garantit que toutes les erreurs d’une catĂ©gorie donnĂ©e sont créées de maniĂšre contrĂŽlĂ©e, documentĂ©e et sĂ©mantiquement cohĂ©rente, qu’elles soient levĂ©es en tant qu’exceptions ou portĂ©es comme Ă©checs d’`Outcome`. +## Rendre les exemples pĂ©dagogiques -## 🎯 PensĂ©e finale +- Utilisez des valeurs simples et rĂ©alistes. +- Rendez la rĂšgle violĂ©e immĂ©diatement visible. +- Appelez la factory documentĂ©e au lieu de recopier un message. +- Gardez les cas limites et de stress dans les tests, pas dans les exemples du catalogue. -FirstClassErrors vise Ă  **exprimer de la connaissance**, pas seulement Ă  gĂ©rer des erreurs. +## Checklist de pull request -Des erreurs bien Ă©crites amĂ©liorent : +Avant de merger une modification liĂ©e aux erreurs, vĂ©rifiez que : -* la lisibilitĂ© du code -* le dĂ©pannage -* la documentation -* la comprĂ©hension partagĂ©e du systĂšme +- [ ] chaque nouvelle factory reprĂ©sente une situation prĂ©cise ; +- [ ] chaque code est spĂ©cifique, stable et unique ; +- [ ] la documentation est liĂ©e avec `[DocumentedBy]` ; +- [ ] les messages publics ne contiennent aucune information interne ou sensible ; +- [ ] les messages de diagnostic expliquent les occurrences concrĂštes ; +- [ ] les donnĂ©es interrogeables utilisent un contexte typĂ© lorsque pertinent ; +- [ ] les diagnostics sont des hypothĂšses et les pistes d’analyse sont actionnables ; +- [ ] les exemples sont rĂ©alistes et appellent la vraie factory ; +- [ ] les documentations anglaise et française restent alignĂ©es lorsqu’elles sont modifiĂ©es ; +- [ ] les modifications de baseline du catalogue sont dĂ©libĂ©rĂ©es et relues lorsque le contrat Ă©volue. --- From 902a8f9afac04333a8e23d63f0caa06f4863431d Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 15:00:33 +0200 Subject: [PATCH 7/9] docs: make the FAQ concise and navigational --- doc/FAQ.en.md | 162 ++++++++++++++------------------------------------ 1 file changed, 45 insertions(+), 117 deletions(-) diff --git a/doc/FAQ.en.md b/doc/FAQ.en.md index 7b7b49d..4ce49ce 100644 --- a/doc/FAQ.en.md +++ b/doc/FAQ.en.md @@ -3,165 +3,93 @@ 🌍 **Languages:** 🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./FAQ.fr.md) -## ❓ Why not just use normal exceptions? +## Why not just use normal exceptions? -You can — and FirstClassErrors still uses standard .NET exceptions. +You can. FirstClassErrors still uses standard .NET exceptions as the mechanism that signals and propagates failures. -The difference is that this library adds: +The library enriches the `Error` carried by that exception with a stable code, structured context, diagnostics, and linked documentation. See [Core Concepts](CoreConcepts.en.md). -* stable error codes -* structured diagnostics -* linked documentation -* a consistent model +## Why not use `Result`? -The exception stays what it has always been: the mechanism that signals and propagates a failure — the library doesn’t replace it. What it transforms is the **error** the exception carries: from a mere *technical signal*, it becomes a *documented knowledge unit*. +A string loses structure. `Outcome` carries the same rich `Error` model used by the exception path: code, messages, context, diagnostics, and documentation identity. -## ❓ Why not use `Result` instead? +See [Usage Patterns](UsagePatterns.en.md) and [Comparison with error-handling libraries](ComparisonWithOtherLibraries.en.md). -A string loses structure. +## Is this too heavy for a simple application? -FirstClassErrors keeps: +It can be. Small scripts, prototypes, and systems without long-term support needs may be better served by standard exceptions. -* error codes -* rich messages -* diagnostics -* context +See [When Not to Use FirstClassErrors](WhenNotToUseFirstClassErrors.en.md) for the decision criteria. -while still allowing errors to be transported without throwing via `Outcome`. +## Why use error factories instead of `new`? -You get the advantages of result-based flow without losing the power of exceptions. +A factory gives one error situation a name, centralizes its code and messages, keeps construction out of the happy path, and acts as the anchor for living documentation. -## ❓ Isn’t this too heavy for simple applications? +See [Getting Started](GettingStarted.en.md). -For small scripts or prototypes, yes, it may be unnecessary. +## What is the difference between error documentation and runtime messages? -This library shines in systems that are: +Documentation describes the stable error category: title, meaning, rule, diagnostic hypotheses, and representative examples. -* domain-heavy -* long-lived -* support-critical -* used by multiple teams +Runtime messages describe or expose a concrete occurrence: -It is an investment in clarity and supportability. +- `ShortMessage` is the safe public summary; +- `DetailedMessage` is optional controlled public detail; +- `DiagnosticMessage` is internal detail for logs and support. -## ❓ Why use error factories instead of `new`? +See [Writing Error Documentation](WritingErrorsGuide.en.md) and [Writing Error Messages](WritingErrorMessages.en.md). -Error factories return `Error` objects (thrown via `.ToException()` when you need an exception). They: +## Are diagnostics the same as root causes? -* make error situations explicit -* keep construction out of the happy path -* centralize messages and codes -* act as anchors for documentation +No. Diagnostics are plausible hypotheses and investigation starting points. They describe what may explain the error without claiming certainty or assigning blame. -They improve readability and enable living documentation. +## Should diagnostics contain support procedures? -## ❓ Are diagnostics the same as root causes? +No. Keep ticketing, escalation, and team-contact instructions outside application error documentation. Analysis leads should say where to investigate, not prescribe an organizational workflow. -No. +See [Writing Error Documentation](WritingErrorsGuide.en.md#6-write-diagnostics-as-hypotheses). -Diagnostics describe **plausible explanations** and guide investigation. -They are hypotheses, not guarantees. +## Why is documentation written in code? -## ❓ Do diagnostics blame developers or users? +Because the documentation is linked to the same factories that create the errors. It can be extracted automatically and evolves beside the behavior it describes, reducing drift. -No. +See [Architecture of the Documentation Pipeline](ArchitectureOfTheDocumentationPipeline.en.md). -Diagnostics should describe states or conditions, not assign blame. +## When should I add `ErrorContext`? -The goal is to support analysis, not responsibility attribution. +Use it for safe, occurrence-specific facts that materially improve diagnosis or observability, such as a business identifier, measured value, or relevant boundary. -## ❓ Why is documentation written in code? +Do not use it for secrets, large payloads, generic documentation, or operational procedures. See [Error Context](ErrorContext.en.md). -Because documentation in code: +## When should I use `Outcome`? -* evolves with the system -* stays close to behavior -* can be extracted automatically +Use it when failure is an expected branch of normal flow, such as validation, parsing, batch processing, or partial success. -This prevents drift between code and documentation. +Use an exception when the failure should interrupt the operation at that level. Both paths can carry the same `Error` created by the same factory. -## ❓ When should I add `ErrorContext` to an error? +See [Usage Patterns](UsagePatterns.en.md). -Use `ErrorContext` for **instance-specific facts** that help diagnosis and observability. Context lives on the `Error`, so it travels with the error whether it is transported via `Outcome` or thrown as an exception. +## Does `Outcome` preserve a stack trace? -Good candidates: +It does not create or throw an exception while the error is carried as data. If the failure is later escalated with `GetResultOrThrow()` or `error.ToException()`, the exception and its stack trace start at that escalation point. -* business identifiers used during investigation -* values that violated a rule -* timestamps or boundaries relevant to the failure +## Can I document every exception? -Avoid adding: +No. Document meaningful application errors: recognized situations, rules, constraints, or boundary failures that benefit from a stable identity and shared explanation. -* sensitive data -* large payloads -* information already present in the stable error documentation +Framework exceptions, accidental crashes, and low-level implementation faults usually remain technical exceptions. See [When Not to Use FirstClassErrors](WhenNotToUseFirstClassErrors.en.md). -A good rule: if the data helps explain this occurrence in logs, and is safe to expose, add it. +## Is FirstClassErrors tied to Domain-Driven Design? -## ❓ When should I use `Outcome`? +No. Its vocabulary aligns well with DDD and hexagonal architecture, but any long-lived system that needs explicit error semantics, supportability, and living documentation can use it. -Use it when failure is expected and part of normal flow: +## Why can a domain error contain only domain errors? -* input validation -* parsing -* batch processing +A `DomainError` states that a business rule was violated. Nesting an infrastructure failure inside it would describe a technical outage as part of the domain vocabulary. -Use exceptions directly when: +A port or infrastructure error may contain a `DomainError` when a boundary-level failure is caused by a domain rejection—for example, an incoming request that cannot be mapped into a valid value object. This preserves both facts without making domain code depend on HTTP, messaging, or another adapter technology. -* invariants are violated -* the system cannot proceed - -## ❓ Does `Outcome` lose the stack trace? - -Yes — intentionally. - -When using `Outcome`, the exception is treated as structured error information, not a runtime crash. -If you later call `GetResultOrThrow()`, the exception is thrown at that point. - -## ❓ Can I document every exception? - -No. - -Focus on meaningful, domain-relevant errors. -Do not document: - -* framework exceptions -* accidental crashes -* low-level technical faults - -The DSL is for errors that carry semantic meaning in your system. - -## ❓ Is this tied to Domain-Driven Design? - -It aligns very well with DDD, but it is not limited to it. - -Any system that benefits from: - -* clear rules -* explicit error semantics -* supportability - -can use this library. - -## ❓ Why can a domain error only nest domain errors, while an infrastructure error can also nest a domain error? - -Because an error's **type follows the nature of the failure — which rule was violated — not the place where the failure is detected.** - -A `DomainError` means a business rule was broken; its cause is only ever another business-rule failure, so it nests only `DomainError`s. Giving it an infrastructure cause would leak a technical concern into the domain vocabulary — and mislabel a technical failure as a business one. - -An infrastructure / port error *can* nest a `DomainError`, because a boundary legitimately reports a *request-level* failure whose *cause* is a domain rule. The textbook case: an incoming (primary-port) adapter maps a DTO into value objects, and a value object refuses to build because an invariant is violated. Two distinct facts coexist: - -* the **cause** — “this value is invalid” — belongs to the domain (the value object produced a `DomainError`); -* the **boundary condition** — “this request is rejected at the edge” — belongs to the adapter (a `PrimaryPortError`). - -Nesting the domain error inside the port error keeps both. It is an *infrastructure error **caused by** a domain error* — not one or the other. - -Mind the direction: the value object stays domain code and emits the `DomainError`; the adapter is what classifies the boundary condition as infrastructure. A value object must never emit an infrastructure error itself — that would make the domain depend on infrastructure. - -Why does the distinction earn its keep? - -* **Transport independence** — the same `DomainError` renders as an HTTP 400/422, a gRPC `INVALID_ARGUMENT`, or a CLI exit code. The HTTP status is a *rendering* of the error, not its identity. -* **Operations** — you alert and retry on `InteractionDirection` and `Transience`, not on the error type alone. A user who typed a bad email produces a non-transient *inbound* port error that must never page anyone; a database timeout produces a *transient* *outbound* one that might. Collapsing invalid input into the same bucket as real outages would poison that signal. +See the taxonomy and nesting rules in [Core Concepts](CoreConcepts.en.md#error-taxonomy). --- From d2ef84bb1f98643f4816db3a1cf72f26371af86a Mon Sep 17 00:00:00 2001 From: Sylvain Aurat Date: Tue, 14 Jul 2026 15:01:19 +0200 Subject: [PATCH 8/9] docs: make the FAQ concise and navigational --- doc/FAQ.fr.md | 164 ++++++++++++++------------------------------------ 1 file changed, 46 insertions(+), 118 deletions(-) diff --git a/doc/FAQ.fr.md b/doc/FAQ.fr.md index dcc7612..232612a 100644 --- a/doc/FAQ.fr.md +++ b/doc/FAQ.fr.md @@ -1,167 +1,95 @@ # FAQ -🌍 **Langues:** +🌍 **Langues :** 🇬🇧 [English](./FAQ.en.md) | đŸ‡«đŸ‡· Français (ce fichier) -## ❓ Pourquoi ne pas simplement utiliser des exceptions normales ? +## Pourquoi ne pas simplement utiliser des exceptions normales ? -C’est possible — et FirstClassErrors repose toujours sur les exceptions standard .NET. +C’est possible. FirstClassErrors utilise toujours les exceptions standard .NET comme mĂ©canisme de signalement et de propagation des dĂ©faillances. -La diffĂ©rence est que cette bibliothĂšque ajoute : +La bibliothĂšque enrichit l’`Error` portĂ©e par l’exception avec un code stable, du contexte structurĂ©, des diagnostics et une documentation liĂ©e. Voir [Concepts fondamentaux](CoreConcepts.fr.md). -* des codes d’erreur stables -* des diagnostics structurĂ©s -* une documentation liĂ©e -* un modĂšle cohĂ©rent +## Pourquoi ne pas utiliser `Result` ? -L’exception reste le mĂ©canisme qui signale et propage une dĂ©faillance — la bibliothĂšque ne la remplace pas. Ce qu’elle transforme, c’est l’**erreur** portĂ©e par l’exception : d’un simple *signal technique*, celle-ci devient une *unitĂ© de connaissance documentĂ©e*. +Une chaĂźne perd la structure. `Outcome` transporte le mĂȘme modĂšle riche d’`Error` que le chemin par exception : code, messages, contexte, diagnostics et identitĂ© documentaire. -## ❓ Pourquoi ne pas utiliser `Result` Ă  la place ? +Voir [Cas d’usage](UsagePatterns.fr.md) et [Comparaison avec les librairies de gestion d’erreurs](ComparisonWithOtherLibraries.fr.md). -Une string perd la structure. +## Est-ce trop lourd pour une application simple ? -FirstClassErrors conserve : +Cela peut l’ĂȘtre. Les petits scripts, prototypes et systĂšmes sans besoin durable de support peuvent ĂȘtre mieux servis par des exceptions standard. -* les codes d’erreur -* des messages riches -* des diagnostics -* du contexte +Voir [Quand ne pas utiliser FirstClassErrors](WhenNotToUseFirstClassErrors.fr.md) pour les critĂšres de dĂ©cision. -tout en permettant de transporter les erreurs sans lever d’exception via `Outcome`. +## Pourquoi utiliser des factories plutĂŽt que `new` ? -Vous obtenez les avantages d’un flux basĂ© sur les rĂ©sultats sans perdre la puissance des exceptions. +Une factory donne un nom Ă  une situation d’erreur, centralise son code et ses messages, garde la construction hors du happy path et sert de point d’ancrage Ă  la documentation vivante. -## ❓ N’est-ce pas trop lourd pour des applications simples ? +Voir [Premiers pas](GettingStarted.fr.md). -Pour de petits scripts ou des prototypes, oui, cela peut ĂȘtre superflu. +## Quelle diffĂ©rence entre la documentation d’erreur et les messages runtime ? -Cette bibliothĂšque prend tout son sens dans des systĂšmes qui sont : +La documentation dĂ©crit la catĂ©gorie d’erreur stable : titre, sens, rĂšgle, hypothĂšses de diagnostic et exemples reprĂ©sentatifs. -* riches en domaine -* conçus pour durer -* critiques pour le support -* utilisĂ©s par plusieurs Ă©quipes +Les messages runtime dĂ©crivent ou exposent une occurrence concrĂšte : -C’est un investissement dans la clartĂ© et la supportabilitĂ©. +- `ShortMessage` est le rĂ©sumĂ© public sĂ»r ; +- `DetailedMessage` est un dĂ©tail public optionnel et maĂźtrisĂ© ; +- `DiagnosticMessage` est le dĂ©tail interne destinĂ© aux logs et au support. -## ❓ Pourquoi utiliser des factories d’erreur plutĂŽt que `new` ? +Voir [Écrire la documentation d’une erreur](WritingErrorsGuide.fr.md) et [Écrire les messages d’une erreur](WritingErrorMessages.fr.md). -Les factories d’erreur retournent des objets `Error` (levĂ©s via `.ToException()` lorsqu’une exception est nĂ©cessaire). Elles : +## Les diagnostics sont-ils des causes racines ? -* rendent explicites les situations d’erreur -* gardent la construction en dehors du happy path -* centralisent messages et codes -* servent de points d’ancrage pour la documentation +Non. Ce sont des hypothĂšses plausibles et des points de dĂ©part pour l’investigation. Ils dĂ©crivent ce qui peut expliquer l’erreur sans prĂ©tendre Ă  la certitude ni attribuer une faute. -Elles amĂ©liorent la lisibilitĂ© et rendent possible la documentation vivante. +## Les diagnostics doivent-ils contenir les procĂ©dures du support ? -## ❓ Les diagnostics sont-ils Ă©quivalents aux causes racines ? +Non. Gardez le ticketing, l’escalade et les consignes de contact d’équipe hors de la documentation applicative. Une piste d’analyse indique oĂč chercher ; elle ne prescrit pas un workflow organisationnel. -Non. +Voir [Écrire la documentation d’une erreur](WritingErrorsGuide.fr.md#6-Ă©crire-les-diagnostics-comme-des-hypothĂšses). -Les diagnostics dĂ©crivent des **explications plausibles** et guident l’investigation. -Ce sont des hypothĂšses, pas des certitudes. +## Pourquoi Ă©crire la documentation dans le code ? -## ❓ Les diagnostics accusent-ils les dĂ©veloppeurs ou les utilisateurs ? +Parce qu’elle est liĂ©e aux mĂȘmes factories qui crĂ©ent les erreurs. Elle peut ĂȘtre extraite automatiquement et Ă©volue Ă  cĂŽtĂ© du comportement qu’elle dĂ©crit, ce qui rĂ©duit la dĂ©rive. -Non. +Voir [Architecture du pipeline de documentation](ArchitectureOfTheDocumentationPipeline.fr.md). -Les diagnostics doivent dĂ©crire des Ă©tats ou des conditions, pas attribuer de responsabilitĂ©. +## Quand ajouter un `ErrorContext` ? -L’objectif est d’aider l’analyse, pas de dĂ©signer des fautifs. +Utilisez-le pour des faits sĂ»rs, propres Ă  une occurrence, qui amĂ©liorent rĂ©ellement le diagnostic ou l’observabilitĂ© : identifiant mĂ©tier, valeur mesurĂ©e ou borne pertinente. -## ❓ Pourquoi la documentation est-elle Ă©crite dans le code ? +N’y placez ni secret, ni payload volumineux, ni documentation gĂ©nĂ©rique, ni procĂ©dure opĂ©rationnelle. Voir [Contexte d’erreur](ErrorContext.fr.md). -Parce que la documentation dans le code : +## Quand utiliser `Outcome` ? -* Ă©volue avec le systĂšme -* reste proche du comportement -* peut ĂȘtre extraite automatiquement +Utilisez-le lorsque l’échec est une branche attendue du flux normal : validation, parsing, traitement par lots ou succĂšs partiel. -Cela Ă©vite la dĂ©rive entre le code et la documentation. +Utilisez une exception lorsque l’échec doit interrompre l’opĂ©ration Ă  ce niveau. Les deux chemins peuvent porter la mĂȘme `Error`, créée par la mĂȘme factory. -## ❓ Quand dois-je ajouter un `ErrorContext` Ă  une erreur ? +Voir [Cas d’usage](UsagePatterns.fr.md). -Utilisez `ErrorContext` pour des **faits spĂ©cifiques Ă  l’occurrence** qui amĂ©liorent le diagnostic et l’observabilitĂ©. Le contexte vit sur l’`Error`, si bien qu’il voyage avec l’erreur, qu’elle soit transportĂ©e via `Outcome` ou levĂ©e en exception. +## `Outcome` conserve-t-il une stack trace ? -Bons candidats : +Aucune exception n’est créée ou levĂ©e tant que l’erreur est transportĂ©e comme donnĂ©e. Si l’échec est ensuite escaladĂ© avec `GetResultOrThrow()` ou `error.ToException()`, l’exception et sa stack trace commencent Ă  ce point d’escalade. -* identifiants mĂ©tier utiles Ă  l’investigation -* valeurs ayant violĂ© une rĂšgle -* dates ou bornes pertinentes pour l’échec +## Puis-je documenter toutes les exceptions ? -Évitez d’y mettre : +Non. Documentez les erreurs applicatives porteuses de sens : situations reconnues, rĂšgles, contraintes ou Ă©checs de frontiĂšre qui bĂ©nĂ©ficient d’une identitĂ© stable et d’une explication partagĂ©e. -* des donnĂ©es sensibles -* des payloads volumineux -* des informations dĂ©jĂ  prĂ©sentes dans la documentation stable de l’erreur +Les exceptions du framework, crashes accidentels et fautes d’implĂ©mentation bas niveau restent gĂ©nĂ©ralement des exceptions techniques. Voir [Quand ne pas utiliser FirstClassErrors](WhenNotToUseFirstClassErrors.fr.md). -RĂšgle simple : si la donnĂ©e aide Ă  expliquer cette occurrence dans les logs, et qu’elle est sĂ»re Ă  exposer, ajoutez-la. +## FirstClassErrors est-il liĂ© au Domain-Driven Design ? -## ❓ Quand dois-je utiliser `Outcome` ? +Non. Son vocabulaire s’accorde bien avec le DDD et l’architecture hexagonale, mais tout systĂšme durable qui a besoin d’une sĂ©mantique d’erreur explicite, de supportabilitĂ© et de documentation vivante peut l’utiliser. -Utilisez-le lorsque l’échec est attendu et fait partie du flux normal : +## Pourquoi une erreur de domaine ne contient-elle que des erreurs de domaine ? -* validation d’entrĂ©es -* parsing -* traitement par lots +Une `DomainError` affirme qu’une rĂšgle mĂ©tier a Ă©tĂ© violĂ©e. Imbriquer une dĂ©faillance d’infrastructure Ă  l’intĂ©rieur dĂ©crirait une panne technique comme une partie du vocabulaire mĂ©tier. -Utilisez directement des exceptions lorsque : +Une erreur de port ou d’infrastructure peut contenir une `DomainError` lorsqu’un Ă©chec Ă  la frontiĂšre est causĂ© par un rejet mĂ©tier, par exemple une requĂȘte entrante impossible Ă  convertir en value object valide. Les deux faits sont ainsi conservĂ©s sans rendre le domaine dĂ©pendant de HTTP, de la messagerie ou d’une technologie d’adapter. -* des invariants sont violĂ©s -* le systĂšme ne peut pas continuer - -## ❓ `Outcome` fait-il perdre la stack trace ? - -Oui — volontairement. - -Avec `Outcome`, l’exception est traitĂ©e comme une information d’erreur structurĂ©e, pas comme un crash runtime. -Si vous appelez ensuite `GetResultOrThrow()`, l’exception est levĂ©e Ă  ce moment-lĂ . - -## ❓ Puis-je documenter toutes les exceptions ? - -Non. - -Concentrez-vous sur les erreurs porteuses de sens au niveau mĂ©tier. -Ne documentez pas : - -* les exceptions du framework -* les crashes accidentels -* les fautes techniques bas niveau - -Le DSL est destinĂ© aux erreurs qui ont une signification sĂ©mantique dans votre systĂšme. - -## ❓ Est-ce liĂ© au Domain-Driven Design ? - -Cela s’aligne trĂšs bien avec le DDD, mais ce n’est pas limitĂ© Ă  ce cadre. - -Tout systĂšme qui bĂ©nĂ©ficie de : - -* rĂšgles claires -* sĂ©mantique d’erreur explicite -* supportabilitĂ© - -peut utiliser cette bibliothĂšque. - -## ❓ Pourquoi une erreur de domaine ne peut-elle imbriquer que des erreurs de domaine, alors qu’une erreur d’infrastructure peut aussi imbriquer une erreur de domaine ? - -Parce que le type d’une erreur **suit la nature de la dĂ©faillance — quelle rĂšgle a Ă©tĂ© violĂ©e — et non l’endroit oĂč la dĂ©faillance est dĂ©tectĂ©e.** - -Une `DomainError` signifie qu’une rĂšgle mĂ©tier a Ă©tĂ© enfreinte ; sa cause n’est jamais qu’une autre dĂ©faillance mĂ©tier, donc elle n’imbrique que des `DomainError`. Lui donner une cause d’infrastructure ferait fuiter une prĂ©occupation technique dans le vocabulaire du domaine — et Ă©tiquetterait une panne technique comme une faute mĂ©tier. - -Une erreur d’infrastructure / de port *peut*, elle, imbriquer une `DomainError`, parce qu’une frontiĂšre signale lĂ©gitimement une dĂ©faillance *au niveau de la requĂȘte* dont la *cause* est une rĂšgle mĂ©tier. Le cas d’école : un adapter entrant (port primaire) mappe un DTO en value objects, et un value object refuse d’ĂȘtre construit parce qu’un invariant est violĂ©. Deux faits distincts coexistent : - -* la **cause** — « cette valeur est invalide » — appartient au domaine (le value object a produit une `DomainError`) ; -* la **condition de frontiĂšre** — « cette requĂȘte est rejetĂ©e au bord » — appartient Ă  l’adapter (une `PrimaryPortError`). - -Imbriquer l’erreur de domaine dans l’erreur de port conserve les deux. C’est une *erreur d’infrastructure **causĂ©e par** une erreur de domaine* — pas l’une ou l’autre. - -Attention au sens : le value object reste du code de domaine et Ă©met la `DomainError` ; c’est l’adapter qui classe la condition de frontiĂšre comme infrastructure. Un value object ne doit jamais Ă©mettre lui-mĂȘme une erreur d’infrastructure — cela ferait dĂ©pendre le domaine de l’infrastructure. - -Pourquoi cette distinction en vaut-elle la peine ? - -* **IndĂ©pendance au transport** — la mĂȘme `DomainError` se rend en HTTP 400/422, en gRPC `INVALID_ARGUMENT`, ou en code de sortie CLI. Le statut HTTP est un *rendu* de l’erreur, pas son identitĂ©. -* **Exploitation** — on alerte et on rejoue sur `InteractionDirection` et `Transience`, pas sur le seul type. Un utilisateur qui saisit un mauvais email produit une erreur de port *entrante* non-transitoire qui ne doit jamais rĂ©veiller personne ; un timeout de base de donnĂ©es produit une erreur *sortante* transitoire qui, elle, le peut. Mettre une saisie invalide dans le mĂȘme panier que de vraies pannes empoisonnerait ce signal. +Voir la taxonomie et les rĂšgles d’imbrication dans [Concepts fondamentaux](CoreConcepts.fr.md#-taxonomie-des-erreurs). --- From 9b393d97828d4d0e3fb6c130c0d58da1500673ab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 06:58:48 +0000 Subject: [PATCH 9/9] docs: gloss message and contract terms at first use Gloss terms a reader meets here before any page defines them, in both languages: - expand RFC 9457 as the Problem Details HTTP error format at its first mention and in the mapping section, and state that the urn:problem type form is an application convention, not a mandate; - introduce the staged builder instead of presenting it as known; - gloss the catalog baseline in the pull-request checklist with a link to catalog versioning; - answer the Result FAQ without assuming the reader already knows Outcome, and say documented definition instead of the opaque documentation identity. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LUaMS5EHzAdQHBJzrYvKTj --- doc/BestPractices.en.md | 2 +- doc/BestPractices.fr.md | 2 +- doc/FAQ.en.md | 2 +- doc/FAQ.fr.md | 2 +- doc/WritingErrorMessages.en.md | 8 +++++--- doc/WritingErrorMessages.fr.md | 8 +++++--- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/doc/BestPractices.en.md b/doc/BestPractices.en.md index 05a6c99..22952eb 100644 --- a/doc/BestPractices.en.md +++ b/doc/BestPractices.en.md @@ -106,7 +106,7 @@ Before merging an error-related change, verify that: - [ ] diagnostics are hypotheses and analysis leads are actionable; - [ ] examples are realistic and call the real factory; - [ ] English and French documentation remain aligned when both are changed; -- [ ] catalog baseline changes are deliberate and reviewed when the contract changes. +- [ ] catalog baseline changes (the committed catalog snapshot — see [Catalog Versioning](CatalogVersioning.en.md)) are deliberate and reviewed when the contract changes. --- diff --git a/doc/BestPractices.fr.md b/doc/BestPractices.fr.md index f65b634..8eac394 100644 --- a/doc/BestPractices.fr.md +++ b/doc/BestPractices.fr.md @@ -106,7 +106,7 @@ Avant de merger une modification liĂ©e aux erreurs, vĂ©rifiez que : - [ ] les diagnostics sont des hypothĂšses et les pistes d’analyse sont actionnables ; - [ ] les exemples sont rĂ©alistes et appellent la vraie factory ; - [ ] les documentations anglaise et française restent alignĂ©es lorsqu’elles sont modifiĂ©es ; -- [ ] les modifications de baseline du catalogue sont dĂ©libĂ©rĂ©es et relues lorsque le contrat Ă©volue. +- [ ] les modifications de baseline du catalogue (le snapshot de catalogue commitĂ© — voir [Versionnage du catalogue](CatalogVersioning.fr.md)) sont dĂ©libĂ©rĂ©es et relues lorsque le contrat Ă©volue. --- diff --git a/doc/FAQ.en.md b/doc/FAQ.en.md index 4ce49ce..94e036a 100644 --- a/doc/FAQ.en.md +++ b/doc/FAQ.en.md @@ -11,7 +11,7 @@ The library enriches the `Error` carried by that exception with a stable code, s ## Why not use `Result`? -A string loses structure. `Outcome` carries the same rich `Error` model used by the exception path: code, messages, context, diagnostics, and documentation identity. +A string loses structure. `Outcome`, the library’s result type, carries the same rich `Error` model used by the exception path: code, messages, context, diagnostics, and a link to its documented definition. See [Usage Patterns](UsagePatterns.en.md) and [Comparison with error-handling libraries](ComparisonWithOtherLibraries.en.md). diff --git a/doc/FAQ.fr.md b/doc/FAQ.fr.md index 232612a..5b87aae 100644 --- a/doc/FAQ.fr.md +++ b/doc/FAQ.fr.md @@ -11,7 +11,7 @@ La bibliothĂšque enrichit l’`Error` portĂ©e par l’exception avec un code sta ## Pourquoi ne pas utiliser `Result` ? -Une chaĂźne perd la structure. `Outcome` transporte le mĂȘme modĂšle riche d’`Error` que le chemin par exception : code, messages, contexte, diagnostics et identitĂ© documentaire. +Une chaĂźne perd la structure. `Outcome`, le type rĂ©sultat de la bibliothĂšque, transporte le mĂȘme modĂšle riche d’`Error` que le chemin par exception : code, messages, contexte, diagnostics et lien vers sa dĂ©finition documentĂ©e. Voir [Cas d’usage](UsagePatterns.fr.md) et [Comparaison avec les librairies de gestion d’erreurs](ComparisonWithOtherLibraries.fr.md). diff --git a/doc/WritingErrorMessages.en.md b/doc/WritingErrorMessages.en.md index 1daf538..d085160 100644 --- a/doc/WritingErrorMessages.en.md +++ b/doc/WritingErrorMessages.en.md @@ -18,7 +18,7 @@ Keeping those audiences separate prevents internal information from leaking whil | `DetailedMessage` | no | public | an optional controlled explanation | | `DiagnosticMessage` | yes | internal | concrete diagnostic information for this occurrence | -They are created through the staged builder: +They are created through a staged builder — a fluent builder whose steps require the diagnostic message and the public short message before an `Error` can exist: ```csharp return DomainError.Create( @@ -39,7 +39,7 @@ It should be: - safe to display directly; - understandable without internal knowledge; -- concise enough for a UI notification or an RFC 9457 `title`; +- concise enough for a UI notification or the `title` of an RFC 9457 (Problem Details for HTTP APIs) response; - stable in meaning, although its wording may evolve or be localized. Good: @@ -136,7 +136,7 @@ See [Error Context](ErrorContext.en.md) for key design and sensitivity guidance. ## HTTP and RFC 9457 -The core error model is HTTP-agnostic. An application may map: +RFC 9457 defines “Problem Details”, the standard error-response format for HTTP APIs. The core error model is HTTP-agnostic. An application may map: | FirstClassErrors value | Typical RFC 9457 field | | --- | --- | @@ -155,6 +155,8 @@ For example: } ``` +The `urn:problem:{service}:{code}` form of `type` is a convention your application chooses (the catalog renderer uses the same one); RFC 9457 only requires a URI. + `DiagnosticMessage` is not a default response field. The application remains responsible for choosing `status`, deciding whether to expose `detail`, and enforcing its security policy. ## Localization diff --git a/doc/WritingErrorMessages.fr.md b/doc/WritingErrorMessages.fr.md index 4a53f40..4fb0e4e 100644 --- a/doc/WritingErrorMessages.fr.md +++ b/doc/WritingErrorMessages.fr.md @@ -18,7 +18,7 @@ SĂ©parer ces publics empĂȘche les fuites d’informations internes tout en conse | `DetailedMessage` | non | externe | une explication optionnelle et maĂźtrisĂ©e | | `DiagnosticMessage` | oui | interne | les informations de diagnostic propres Ă  cette occurrence | -Ils sont créés avec le builder Ă©tagĂ© : +Ils sont créés avec un builder Ă©tagĂ© — un builder fluide dont les Ă©tapes exigent le message de diagnostic puis le message public court avant qu’une `Error` puisse exister : ```csharp return DomainError.Create( @@ -39,7 +39,7 @@ Il doit ĂȘtre : - affichable directement sans risque ; - comprĂ©hensible sans connaissance interne ; -- assez concis pour une notification UI ou le `title` d’un problem detail RFC 9457 ; +- assez concis pour une notification UI ou le `title` d’une rĂ©ponse RFC 9457 (« Problem Details », le format d’erreur standard des API HTTP) ; - stable dans son sens, mĂȘme si sa formulation Ă©volue ou est localisĂ©e. Bon : @@ -136,7 +136,7 @@ Voir [Contexte d’erreur](ErrorContext.fr.md) pour la conception des clĂ©s et l ## HTTP et RFC 9457 -Le modĂšle d’erreur du cƓur est agnostique vis-Ă -vis d’HTTP. Une application peut mapper : +La RFC 9457 dĂ©finit « Problem Details », le format standard de rĂ©ponse d’erreur des API HTTP. Le modĂšle d’erreur du cƓur est agnostique vis-Ă -vis d’HTTP. Une application peut mapper : | Valeur FirstClassErrors | Champ RFC 9457 courant | | --- | --- | @@ -155,6 +155,8 @@ Exemple : } ``` +La forme `urn:problem:{service}:{code}` du champ `type` est une convention choisie par votre application (le renderer du catalogue utilise la mĂȘme) ; la RFC 9457 exige seulement une URI. + `DiagnosticMessage` n’est pas un champ de rĂ©ponse par dĂ©faut. L’application reste responsable du choix de `status`, de la dĂ©cision d’exposer ou non `detail` et de l’application de sa politique de sĂ©curitĂ©. ## Localisation