diff --git a/doc/ArchitectureOfTheDocumentationPipeline.en.md b/doc/ArchitectureOfTheDocumentationPipeline.en.md index b9c236d..35972e7 100644 --- a/doc/ArchitectureOfTheDocumentationPipeline.en.md +++ b/doc/ArchitectureOfTheDocumentationPipeline.en.md @@ -3,183 +3,179 @@ 🌍 **Languages:** 🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./ArchitectureOfTheDocumentationPipeline.fr.md) -FirstClassErrors does not treat documentation as an external artifact. -Documentation is derived directly from the code and flows through a structured pipeline. - -The pipeline separates **knowledge definition**, **extraction**, and **rendering**. - -## đŸ§± 1. Knowledge lives in the code - -Error knowledge is written where errors are defined: +FirstClassErrors derives error documentation from the code that defines and creates errors. The pipeline keeps five responsibilities separate: defining knowledge, extracting it, isolating execution, aggregating a catalog, and rendering files. + +## The pipeline at a glance + +```mermaid +flowchart LR + A[Error factories and DescribeError DSL] + B[Assembly extraction] + C[Isolated worker process] + D[Solution aggregation] + E[Renderer] + F[Markdown, HTML, JSON, or custom output] + + A --> B + B --> C + C --> D + D --> E + E --> F +``` -* A static class annotated with `[ProvidesErrorsFor(...)]` groups the errors that belong to a given model -* `Error` subtypes (`DomainError`, `PrimaryPortError`, `SecondaryPortError`, ...) represent categories of errors -* Factory methods represent specific error situations -* The `DescribeError` DSL describes meaning, rules, diagnostics, and examples +The important boundary is this: -At this stage, documentation is **structured data**, not text files. +> application code defines structured error knowledge; renderers only decide how that knowledge is presented. -## 🔗 2. Errors are anchored and linked to documentation +## 1. Knowledge is defined next to the error -A static class declares that it owns the errors of a given model: +A static class groups the errors associated with one source: ```csharp [ProvidesErrorsFor(nameof(Temperature))] -public static class InvalidTemperatureError { ... } +public static class InvalidTemperatureError { + // factories, documentation methods, and codes +} ``` -This attribute is the primary anchor of the documentation model: it marks the class as a source of errors and supplies `ErrorDocumentation.Source` (the model name passed via `nameof(...)`). It can also carry an optional `Description`, rendered as an introduction to that source's group in the generated documentation: +Each factory represents one recognized error situation. `[DocumentedBy]` links that factory to a documentation method: ```csharp -[ProvidesErrorsFor(nameof(Temperature), - Description = "Errors raised when constructing a Temperature value from an out-of-range input.")] +[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))] +internal static DomainError BelowAbsoluteZero(decimal value) { + // create the Error +} ``` -The `Description` is literal text by default; set `DescriptionResourceType` to have it resolved as a resource key instead, for localization (see [Internationalization](Internationalization.en.md)). - -Inside that class, each factory method is linked to its documentation method using: - ```csharp -[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))] +private static ErrorDocumentation BelowAbsoluteZeroDocumentation() { + return DescribeError + .WithTitle("Temperature below absolute zero") + .WithDescription("This error occurs when a temperature is below the physical minimum.") + .WithRule("A temperature cannot be lower than absolute zero.") + .WithDiagnostic( + "A converted or computed temperature fell below the physical minimum.", + ErrorOrigin.Internal, + "Check the computation or conversion that produced the value.") + .WithExamples(() => BelowAbsoluteZero(-1m)); +} ``` -This creates an explicit connection between: +At this point there is no Markdown, HTML, or JSON. There is structured knowledge expressed in code. -* how an error is created -* how it is described +## 2. Extraction turns executable documentation into data -## 🔎 3. Extraction +The extractor finds `[ProvidesErrorsFor]` classes, resolves `[DocumentedBy]` links, and invokes the documentation methods and their example factories. -`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` scans a single assembly and: +This matters because examples are produced by the real factory code. They are not copied strings that can silently drift from runtime behavior. -* finds any class annotated with `[ProvidesErrorsFor(...)]` (these are plain static classes, not exception types) -* finds factory methods marked with `[DocumentedBy]` -* **invokes** the linked documentation methods — and the example factories they reference. Documentation is *executable*, so the examples reflect the real code rather than a copy that can drift. A factory that throws, or a `[DocumentedBy]` reference that cannot be resolved, is recorded as a failure instead of aborting the whole scan. -* returns an `ErrorDocumentationExtractionResult`: the `ErrorDocumentation` collection (deduped by `Code`, ordered by `Code`) together with the list of extraction `Failures` +The result is an in-memory catalog of `ErrorDocumentation` objects plus any extraction failures. -At this stage, documentation becomes a structured in-memory model. +## 3. Workers isolate target execution -## đŸ§Ș 4. Extraction runs out of process +Extraction executes application code. For that reason, solution-level generation does not load every target assembly into one long-lived process. It starts a short-lived worker for each assembly. -Because extraction **executes** the target's code, each assembly is documented by a short-lived **worker process**, spawned by the generator (`dotnet exec`, using the target's own dependency file). This buys: +That isolation provides: -* **living examples** — the example factories run against the real code, not a stale description -* a **fresh static registry** per assembly — no state leaks from one assembly to the next -* **version isolation** — each target binds its own FirstClassErrors version -* **fault isolation** — a crashing or hanging assembly is killed on a timeout and recorded as a failure, without taking the whole run down +- a fresh static state for each assembly; +- dependency and FirstClassErrors version isolation; +- containment of crashes and hangs; +- a clear timeout boundary; +- failure reporting without necessarily losing the whole run. -The worker writes its `ErrorDocumentationExtractionResult` as JSON; the generator reads it back and moves on to the next assembly. +The worker serializes the extraction result to JSON, then the generator continues with the next assembly. -## đŸ§© 5. Aggregation at solution level +For exact discovery, opt-in, timeout, and failure-policy rules, see [Extraction and Project Discovery Reference](DocumentationExtractionReference.en.md). -`SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(solutionPath[, options])` — or `GetErrorDocumentationFromAssemblies(paths, options)` for pre-built binaries — works at a higher level and: +## 4. The generator builds one catalog -* discovers the projects (via `dotnet sln list`), keeps the ones that opt in (see below), and — unless told not to — builds them -* runs a worker for each output assembly -* aggregates all extracted `ErrorDocumentation` (deduped by `Code`, ordered by `Code`) +At solution level, the generator: -This produces a **global error catalog** for the application or system. +1. builds the solution unless `--no-build` is used; +2. discovers the projects that participate in documentation generation; +3. starts one worker per selected output assembly; +4. collects documentation and extraction failures; +5. deduplicates and orders errors by code. -### Opting a project in +The output of this stage is one global catalog for the selected application or set of assemblies. -Solution-level generation is **opt-in per project**: a project is documented only when its project file (`.csproj`) sets the MSBuild property +The CLI exposes the common path: -```xml - - true - +```bash +fce generate \ + --solution ./MyApp.sln \ + --format markdown \ + --layout split \ + --service-name my-api \ + --output ./docs/errors ``` -Each project discovered in the solution is then treated as follows: - -| `GenerateErrorDocumentation` | Result | -| ---------------------------- | ------ | -| `true` | documented | -| absent | skipped — the default is opt-in | -| `false` | always skipped, even when the "include everything" policy is on | -| declared twice, or `Condition`-gated | reported, never guessed — a warning that skips the project (`Continue`), or a hard error | +## 5. Renderers turn the catalog into files -This keeps the catalog — and the worker processes spawned to build it — scoped to the projects that actually define application errors, rather than every project in the solution. - -The property is a **marker read straight from the project file**, not an MSBuild build switch: nothing consumes it at plain `dotnet build` time, and passing `-p:GenerateErrorDocumentation=
` on a build command line has no effect. Because it is read from the project XML rather than evaluated by MSBuild, it must be declared literally in the `.csproj`: a value inherited from a shared `Directory.Build.props` or brought in by an import is not seen, so the project is treated as if the marker were absent. If the marker *is* in the `.csproj` but its effective value is unknowable from the XML alone — declared more than once, or gated behind a `Condition` — GenDoc does not guess: it reports the project through the configured failure behavior (a warning that skips it under `Continue`, a hard error otherwise). The `--assemblies` path is not subject to this filter — it documents exactly the binaries you name. - -> For programmatic callers, `SolutionGenerationOptions` exposes `OptInPropertyName` (rename the marker) and `IncludeProjectsWithoutOptIn` (document every project regardless). The `fce` CLI uses the defaults shown above. - -## đŸ–šïž 6. Rendering to output formats - -A renderer turns the in-memory catalog into published documentation. Because the model is plain data, rendering is decoupled behind a single contract: +A renderer receives the structured catalog and a `RenderRequest`. It returns one or more `RenderedDocument` values. ```csharp public interface IErrorDocumentationRenderer { string Format { get; } IReadOnlyCollection SupportedLayouts { get; } - IReadOnlyList Render(IEnumerable catalog, RenderRequest request); + IReadOnlyList Render( + IEnumerable catalog, + RenderRequest request); } ``` -Each renderer declares the layouts it can produce and is asked for one per call through the `RenderRequest` (which also carries the target culture); an unsupported layout is rejected with a `LayoutNotSupportedException`. Three renderers ship in the box: +Built-in renderers currently provide: -* **json** — a curated, stable JSON schema (`single` layout only) -* **markdown** — a single file, or (with `--layout split`) a README index plus one file per source group and one file per error (`single`/`split`) -* **html** — a self-contained static site: a searchable table of contents grouped by source and, in `split`, one page per error (`single`/`split`). See [The HTML renderer](TheHtmlRenderer.en.md). +| Format | Purpose | Layouts | +| --- | --- | --- | +| `json` | stable machine-readable catalog | `single` | +| `markdown` | repository or portal documentation | `single`, `split` | +| `html` | self-contained searchable static documentation | `single`, `split` | -Any other format (CSV, a company template, 
) is a **custom renderer**: implement the interface and register it. See [Writing a custom renderer](WritingACustomRenderer.en.md). +`single` produces one document; `split` produces one page per error. -## 🧰 7. CLI orchestration +Custom renderers use the same contract. See [Writing a custom renderer](WritingACustomRenderer.en.md). -The `fce` CLI orchestrates the whole process: +## 6. Culture crosses two distinct boundaries -```bash -fce generate --solution ./MyApp.sln --format markdown --layout split --output ./docs/errors -``` +Internationalization is deliberately split: -It handles the solution build, extraction (via workers), aggregation and rendering. Common options can be stored in a configuration file (`fce.json`) so they need not be repeated, and custom renderers are referenced there too: +- **extraction culture** localizes error content produced by factories and documentation methods; +- **render culture** localizes headings, labels, and other renderer-owned boilerplate. -```bash -fce config init -fce config renderer add ./plugins/MyCompany.Renderers.dll -fce generate # uses the configured solution, format, output, renderers
 -``` - -A value passed on the command line overrides the configuration. - -## 🌍 8. Internationalization +Stable identifiers remain culture-invariant: codes, source identities, context-key names, generated paths, anchors, and internal diagnostic messages. -The pipeline is culture-aware at two levels: the extractor localizes error *content* (under the requested UI culture) and each renderer localizes its own *templates* (from `RenderRequest.Culture`), while file names and anchors stay culture-invariant so links never break. It is opt-in and driven by `fce generate --language`. +See [Internationalization](Internationalization.en.md) for the complete workflow. -See **[Internationalization](Internationalization.en.md)** for the full story — choosing the language, the `DescriptionResourceType` hook, localizing renderer templates, and driving it without the CLI. +## Why the separation matters -## 🔁 Why this architecture matters +| Component | Owns | +| --- | --- | +| application code | error meaning, rules, diagnostics, examples, public messages | +| extractor | discovery and execution of documented factories | +| worker | process and dependency isolation | +| generator | build, selection, aggregation, ordering, failure collection | +| renderer | file format, layout, template text | +| CLI | orchestration and configuration | -This separation ensures: +This prevents several forms of coupling: -| Layer | Responsibility | -| --------- | ------------------------------------- | -| Code | Define error knowledge | -| Reader | Extract structured documentation | -| Worker | Execute the code in isolation | -| Generator | Build and aggregate across assemblies | -| Renderer | Turn the catalog into a target format | -| CLI | Orchestrate the process | +- factories do not know whether the output is Markdown or HTML; +- renderers do not execute application factories; +- one failing assembly does not have to corrupt every other extraction; +- localized content and localized presentation remain independent; +- programmatic callers can use individual pipeline stages without the CLI. -Documentation remains: +## The key idea -* close to the code -* always up to date -* structured -* tool-friendly +> Error documentation is not manually rewritten from the system. It is derived from the same factories and structured descriptions that define the system's recognized failures. -## 🎯 The key idea - -> Error documentation is not written *about* the system. -> It is derived *from* the system. - -The code is the source of truth. +The code remains the source of truth; the pipeline makes that knowledge portable. --- ---- +--- \ No newline at end of file diff --git a/doc/ArchitectureOfTheDocumentationPipeline.fr.md b/doc/ArchitectureOfTheDocumentationPipeline.fr.md index 79eb45e..e7370b4 100644 --- a/doc/ArchitectureOfTheDocumentationPipeline.fr.md +++ b/doc/ArchitectureOfTheDocumentationPipeline.fr.md @@ -1,185 +1,181 @@ # Architecture du pipeline de documentation -🌍 **Langues:** +🌍 **Langues :** 🇬🇧 [English](./ArchitectureOfTheDocumentationPipeline.en.md) | đŸ‡«đŸ‡· Français (ce fichier) -FirstClassErrors ne considĂšre pas la documentation comme un artefact externe. -La documentation est dĂ©rivĂ©e directement du code et circule Ă  travers un pipeline structurĂ©. +FirstClassErrors dĂ©rive la documentation des erreurs du code qui les dĂ©finit et les crĂ©e. Le pipeline sĂ©pare cinq responsabilitĂ©s : dĂ©finir la connaissance, l’extraire, isoler son exĂ©cution, agrĂ©ger un catalogue et produire les fichiers. -Le pipeline sĂ©pare la **dĂ©finition de la connaissance**, l’**extraction** et le **rendu**. +## Le pipeline en un coup d’Ɠil -## đŸ§± 1. La connaissance vit dans le code +```mermaid +flowchart LR + A[Factories d'erreur et DSL DescribeError] + B[Extraction de l'assembly] + C[Processus worker isolĂ©] + D[AgrĂ©gation de la solution] + E[Renderer] + F[Sortie Markdown, HTML, JSON ou personnalisĂ©e] -La connaissance liĂ©e aux erreurs est Ă©crite Ă  l’endroit oĂč les erreurs sont dĂ©finies : + A --> B + B --> C + C --> D + D --> E + E --> F +``` -* Une classe statique annotĂ©e avec `[ProvidesErrorsFor(...)]` regroupe les erreurs liĂ©es Ă  un modĂšle donnĂ© -* Les sous-types d’`Error` (`DomainError`, `PrimaryPortError`, `SecondaryPortError`, ...) reprĂ©sentent des catĂ©gories d’erreurs -* Les mĂ©thodes factory reprĂ©sentent des situations d’erreur spĂ©cifiques -* Le DSL `DescribeError` dĂ©crit le sens, les rĂšgles, les diagnostics et les exemples +La frontiĂšre essentielle est la suivante : -À ce stade, la documentation est une **donnĂ©e structurĂ©e**, pas des fichiers texte. +> le code applicatif dĂ©finit une connaissance structurĂ©e sur les erreurs ; les renderers dĂ©cident uniquement de sa prĂ©sentation. -## 🔗 2. Les erreurs sont ancrĂ©es et liĂ©es Ă  la documentation +## 1. La connaissance est dĂ©finie Ă  cĂŽtĂ© de l’erreur -Une classe statique dĂ©clare qu’elle possĂšde les erreurs d’un modĂšle donnĂ© : +Une classe statique regroupe les erreurs associĂ©es Ă  une source : ```csharp [ProvidesErrorsFor(nameof(Temperature))] -public static class InvalidTemperatureError { ... } +public static class InvalidTemperatureError { + // factories, mĂ©thodes de documentation et codes +} ``` -Cet attribut est le point d’ancrage principal du modĂšle de documentation : il marque la classe comme source d’erreurs et fournit `ErrorDocumentation.Source` (le nom du modĂšle passĂ© via `nameof(...)`). Il peut aussi porter une `Description` optionnelle, rendue comme introduction au groupe de cette source dans la documentation gĂ©nĂ©rĂ©e : +Chaque factory reprĂ©sente une situation d’erreur reconnue. `[DocumentedBy]` lie cette factory Ă  une mĂ©thode de documentation : ```csharp -[ProvidesErrorsFor(nameof(Temperature), - Description = "Errors raised when constructing a Temperature value from an out-of-range input.")] +[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))] +internal static DomainError BelowAbsoluteZero(decimal value) { + // crĂ©ation de l'Error +} ``` -La `Description` est un texte littĂ©ral par dĂ©faut ; renseignez `DescriptionResourceType` pour qu’elle soit rĂ©solue comme une clĂ© de ressource Ă  la place, en vue de la localisation (voir [Internationalisation](Internationalisation.fr.md)). - -À l’intĂ©rieur de cette classe, chaque mĂ©thode factory est liĂ©e Ă  sa mĂ©thode de documentation via : - ```csharp -[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))] +private static ErrorDocumentation BelowAbsoluteZeroDocumentation() { + return DescribeError + .WithTitle("TempĂ©rature sous le zĂ©ro absolu") + .WithDescription("Cette erreur survient lorsqu'une tempĂ©rature est infĂ©rieure Ă  la limite physique.") + .WithRule("Une tempĂ©rature ne peut pas ĂȘtre infĂ©rieure au zĂ©ro absolu.") + .WithDiagnostic( + "Une tempĂ©rature convertie ou calculĂ©e est passĂ©e sous la limite physique.", + ErrorOrigin.Internal, + "VĂ©rifiez le calcul ou la conversion qui a produit la valeur.") + .WithExamples(() => BelowAbsoluteZero(-1m)); +} ``` -Cela crĂ©e une connexion explicite entre : +À ce stade, il n’existe encore ni Markdown, ni HTML, ni JSON. Il existe une connaissance structurĂ©e exprimĂ©e dans le code. -* la maniĂšre dont une erreur est créée -* la maniĂšre dont elle est dĂ©crite +## 2. L’extraction transforme la documentation exĂ©cutable en donnĂ©es -## 🔎 3. Extraction +L’extracteur trouve les classes `[ProvidesErrorsFor]`, rĂ©sout les liens `[DocumentedBy]`, puis invoque les mĂ©thodes de documentation et leurs factories d’exemples. -`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` analyse un assembly et : +C’est important parce que les exemples sont produits par le vrai code des factories. Ce ne sont pas des chaĂźnes copiĂ©es qui pourraient dĂ©river silencieusement du comportement runtime. -* trouve toute classe annotĂ©e avec `[ProvidesErrorsFor(...)]` (ce sont de simples classes statiques, pas des types d’exception) -* trouve les mĂ©thodes factory marquĂ©es avec `[DocumentedBy]` -* **invoque** les mĂ©thodes de documentation liĂ©es — ainsi que les factories d’exemples qu’elles rĂ©fĂ©rencent. La documentation est *exĂ©cutable* : les exemples reflĂštent le vrai code, et non une copie qui pourrait dĂ©river. Une factory qui lĂšve une exception, ou une rĂ©fĂ©rence `[DocumentedBy]` non rĂ©solvable, est enregistrĂ©e comme une *failure* au lieu d’interrompre toute l’analyse. -* renvoie un `ErrorDocumentationExtractionResult` : la collection d’`ErrorDocumentation` (dĂ©dupliquĂ©e par `Code`, ordonnĂ©e par `Code`) avec la liste des *failures* d’extraction +Le rĂ©sultat est un catalogue en mĂ©moire d’objets `ErrorDocumentation`, accompagnĂ© des Ă©ventuels Ă©checs d’extraction. -À ce stade, la documentation devient un modĂšle structurĂ© en mĂ©moire. +## 3. Les workers isolent l’exĂ©cution des cibles -## đŸ§Ș 4. L’extraction s’exĂ©cute hors-processus +L’extraction exĂ©cute du code applicatif. Pour cette raison, la gĂ©nĂ©ration au niveau solution ne charge pas tous les assemblies cibles dans un unique processus longue durĂ©e. Elle dĂ©marre un worker Ă©phĂ©mĂšre pour chaque assembly. -Parce que l’extraction **exĂ©cute** le code de la cible, chaque assembly est documentĂ© par un **processus worker** Ă©phĂ©mĂšre, lancĂ© par le gĂ©nĂ©rateur (`dotnet exec`, en s’appuyant sur le fichier de dĂ©pendances de la cible). On y gagne : +Cette isolation apporte : -* des **exemples vivants** — les factories d’exemples s’exĂ©cutent contre le vrai code, pas une description figĂ©e -* un **registre statique neuf** par assembly — aucun Ă©tat ne fuit d’un assembly Ă  l’autre -* l’**isolation des versions** — chaque cible lie sa propre version de FirstClassErrors -* l’**isolation des pannes** — un assembly qui plante ou se bloque est tuĂ© sur *timeout* et enregistrĂ© comme une *failure*, sans faire tomber toute la gĂ©nĂ©ration +- un Ă©tat statique neuf pour chaque assembly ; +- l’isolation des dĂ©pendances et de la version de FirstClassErrors ; +- le confinement des crashs et blocages ; +- une frontiĂšre de timeout explicite ; +- le signalement des Ă©checs sans nĂ©cessairement perdre toute l’exĂ©cution. -Le worker Ă©crit son `ErrorDocumentationExtractionResult` en JSON ; le gĂ©nĂ©rateur le relit et passe Ă  l’assembly suivant. +Le worker sĂ©rialise le rĂ©sultat d’extraction en JSON, puis le gĂ©nĂ©rateur passe Ă  l’assembly suivant. -## đŸ§© 5. AgrĂ©gation au niveau de la solution +Pour les rĂšgles exactes de dĂ©couverte, d’opt-in, de timeout et de politique d’échec, voir la [RĂ©fĂ©rence de l’extraction et de la dĂ©couverte des projets](DocumentationExtractionReference.fr.md). -`SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(solutionPath[, options])` — ou `GetErrorDocumentationFromAssemblies(paths, options)` pour des binaires dĂ©jĂ  compilĂ©s — travaille Ă  un niveau plus Ă©levĂ© et : +## 4. Le gĂ©nĂ©rateur construit un catalogue unique -* dĂ©couvre les projets (via `dotnet sln list`), ne garde que ceux qui ont optĂ© (voir plus bas) et, sauf indication contraire, les compile -* lance un worker pour chaque assembly de sortie -* agrĂšge tous les `ErrorDocumentation` extraits (dĂ©dupliquĂ©s par `Code`, ordonnĂ©s par `Code`) +Au niveau de la solution, le gĂ©nĂ©rateur : -Cela produit un **catalogue global des erreurs** pour l’application ou le systĂšme. +1. compile la solution, sauf avec `--no-build` ; +2. dĂ©couvre les projets qui participent Ă  la gĂ©nĂ©ration documentaire ; +3. dĂ©marre un worker par assembly de sortie sĂ©lectionnĂ© ; +4. collecte la documentation et les Ă©checs d’extraction ; +5. dĂ©duplique et ordonne les erreurs par code. -### Activer un projet (opt-in) +La sortie de cette Ă©tape est un catalogue global pour l’application ou l’ensemble d’assemblies sĂ©lectionnĂ©. -La gĂ©nĂ©ration au niveau de la solution est **opt-in par projet** : un projet n’est documentĂ© que si son fichier projet (`.csproj`) dĂ©finit la propriĂ©tĂ© MSBuild +La CLI expose le parcours courant : -```xml - - true - +```bash +fce generate \ + --solution ./MyApp.sln \ + --format markdown \ + --layout split \ + --service-name my-api \ + --output ./docs/errors ``` -Chaque projet dĂ©couvert dans la solution est alors traitĂ© ainsi : - -| `GenerateErrorDocumentation` | RĂ©sultat | -| ---------------------------- | -------- | -| `true` | documentĂ© | -| absente | ignorĂ© — le dĂ©faut est l’opt-in | -| `false` | toujours ignorĂ©, mĂȘme quand la politique « tout inclure » est active | -| dĂ©clarĂ©e deux fois, ou sous `Condition` | signalĂ©, jamais devinĂ© — un avertissement qui ignore le projet (`Continue`), ou une erreur bloquante | - -Cela limite le catalogue — et les workers lancĂ©s pour le produire — aux projets qui dĂ©finissent rĂ©ellement des erreurs applicatives, plutĂŽt qu’à tous les projets de la solution. - -La propriĂ©tĂ© est un **marqueur lu directement dans le fichier projet**, pas un interrupteur de build MSBuild : rien ne la consomme lors d’un simple `dotnet build`, et passer `-p:GenerateErrorDocumentation=
` sur une ligne de commande de build n’a aucun effet. Parce qu’elle est lue dans le XML du projet plutĂŽt qu’évaluĂ©e par MSBuild, elle doit ĂȘtre dĂ©clarĂ©e littĂ©ralement dans le `.csproj` : une valeur hĂ©ritĂ©e d’un `Directory.Build.props` partagĂ© ou apportĂ©e par un import n’est pas vue, et le projet est alors traitĂ© comme si le marqueur Ă©tait absent. Si le marqueur *est* dans le `.csproj` mais que sa valeur effective ne peut pas ĂȘtre connue depuis le seul XML — dĂ©clarĂ© plusieurs fois, ou conditionnĂ© par un `Condition` — GenDoc ne devine pas : il signale le projet via le comportement d’échec configurĂ© (un avertissement qui l’ignore en mode `Continue`, une erreur bloquante sinon). Le mode `--assemblies` n’est pas soumis Ă  ce filtre : il documente exactement les binaires que vous nommez. +## 5. Les renderers transforment le catalogue en fichiers -> Pour les appels programmatiques, `SolutionGenerationOptions` expose `OptInPropertyName` (renommer le marqueur) et `IncludeProjectsWithoutOptIn` (documenter tous les projets sans distinction). La CLI `fce` utilise les valeurs par dĂ©faut ci-dessus. - -## đŸ–šïž 6. Rendu vers des formats de sortie - -Un *renderer* transforme le catalogue en mĂ©moire en documentation publiĂ©e. Le modĂšle Ă©tant une simple donnĂ©e, le rendu est dĂ©couplĂ© derriĂšre un contrat unique : +Un renderer reçoit le catalogue structurĂ© et un `RenderRequest`. Il renvoie un ou plusieurs `RenderedDocument`. ```csharp public interface IErrorDocumentationRenderer { string Format { get; } IReadOnlyCollection SupportedLayouts { get; } - IReadOnlyList Render(IEnumerable catalog, RenderRequest request); + IReadOnlyList Render( + IEnumerable catalog, + RenderRequest request); } ``` -Chaque renderer dĂ©clare les layouts qu’il sait produire et on lui en demande un Ă  chaque appel via le `RenderRequest` (qui porte aussi la culture cible) ; un layout non pris en charge est rejetĂ© par une `LayoutNotSupportedException`. Trois renderers sont fournis d’origine : +Les renderers intĂ©grĂ©s fournissent actuellement : -* **json** — un schĂ©ma JSON curĂ© et stable (layout `single` uniquement) -* **markdown** — un fichier unique, ou (avec `--layout split`) un index README plus un fichier par groupe de source et un fichier par erreur (`single`/`split`) -* **html** — un site statique autonome : une table des matiĂšres consultable et groupĂ©e par source et, en `split`, une page par erreur (`single`/`split`). Voir [Le renderer HTML](TheHtmlRenderer.fr.md). +| Format | Usage | Layouts | +| --- | --- | --- | +| `json` | catalogue stable lisible par machine | `single` | +| `markdown` | documentation de dĂ©pĂŽt ou de portail | `single`, `split` | +| `html` | documentation statique autonome et consultable | `single`, `split` | -Tout autre format (CSV, un gabarit maison, 
) est un **renderer personnalisĂ©** : implĂ©mentez l’interface et enregistrez-le. Voir [Écrire son propre renderer](WritingACustomRenderer.fr.md). +`single` produit un document unique ; `split` produit une page par erreur. -## 🧰 7. Orchestration via CLI +Les renderers personnalisĂ©s utilisent le mĂȘme contrat. Voir [Écrire son propre renderer](WritingACustomRenderer.fr.md). -La CLI `fce` orchestre l’ensemble du processus : +## 6. La culture traverse deux frontiĂšres distinctes -```bash -fce generate --solution ./MyApp.sln --format markdown --layout split --output ./docs/errors -``` +L’internationalisation est volontairement sĂ©parĂ©e : -Elle gĂšre la compilation de la solution, l’extraction (via les workers), l’agrĂ©gation et le rendu. Les options courantes peuvent ĂȘtre stockĂ©es dans un fichier de configuration (`fce.json`) pour ne pas les rĂ©pĂ©ter, et les renderers personnalisĂ©s y sont aussi rĂ©fĂ©rencĂ©s : +- la **culture d’extraction** localise le contenu des erreurs produit par les factories et mĂ©thodes de documentation ; +- la **culture de rendu** localise les titres, libellĂ©s et autres textes fixes appartenant au renderer. -```bash -fce config init -fce config renderer add ./plugins/MyCompany.Renderers.dll -fce generate # utilise la solution, le format, l’output, les renderers configurĂ©s
 -``` +Les identifiants stables restent indĂ©pendants de la culture : codes, identitĂ©s de source, noms de clĂ©s de contexte, chemins gĂ©nĂ©rĂ©s, ancres et messages de diagnostic internes. -Une valeur passĂ©e en ligne de commande Ă©crase la configuration. +Voir [Internationalisation](Internationalisation.fr.md) pour le workflow complet. -## 🌍 8. Internationalisation +## Pourquoi cette sĂ©paration est importante -Le pipeline est sensible Ă  la culture Ă  deux niveaux : l’extracteur localise le *contenu* des erreurs (sous la culture UI demandĂ©e) et chaque renderer localise ses propres *gabarits* (depuis `RenderRequest.Culture`), tandis que les noms de fichiers et les ancres restent indĂ©pendants de la culture, pour que les liens ne cassent jamais. C’est optionnel et pilotĂ© par `fce generate --language`. +| Composant | ResponsabilitĂ© | +| --- | --- | +| code applicatif | sens des erreurs, rĂšgles, diagnostics, exemples, messages publics | +| extracteur | dĂ©couverte et exĂ©cution des factories documentĂ©es | +| worker | isolation du processus et des dĂ©pendances | +| gĂ©nĂ©rateur | compilation, sĂ©lection, agrĂ©gation, ordre, collecte des Ă©checs | +| renderer | format de fichier, layout, texte du gabarit | +| CLI | orchestration et configuration | -Voir **[Internationalisation](Internationalisation.fr.md)** pour le dĂ©tail — choisir la langue, le hook `DescriptionResourceType`, la localisation des gabarits de renderer, et le pilotage sans la CLI. +Cette sĂ©paration Ă©vite plusieurs couplages : -## 🔁 Pourquoi cette architecture est importante +- les factories ignorent si la sortie sera du Markdown ou du HTML ; +- les renderers n’exĂ©cutent pas les factories applicatives ; +- un assembly dĂ©faillant ne doit pas corrompre toutes les autres extractions ; +- le contenu localisĂ© et la prĂ©sentation localisĂ©e restent indĂ©pendants ; +- les appels programmatiques peuvent utiliser les Ă©tapes du pipeline sans passer par la CLI. -Cette sĂ©paration garantit : +## L’idĂ©e clĂ© -| Couche | ResponsabilitĂ© | -| ----------- | --------------------------------------- | -| Code | DĂ©finir la connaissance des erreurs | -| Reader | Extraire la documentation structurĂ©e | -| Worker | ExĂ©cuter le code de façon isolĂ©e | -| GĂ©nĂ©rateur | Compiler et agrĂ©ger Ă  travers les assemblies | -| Renderer | Transformer le catalogue en un format cible | -| CLI | Orchestrer le processus | +> La documentation des erreurs n’est pas réécrite manuellement Ă  partir du systĂšme. Elle est dĂ©rivĂ©e des mĂȘmes factories et descriptions structurĂ©es qui dĂ©finissent les Ă©checs reconnus par le systĂšme. -La documentation reste : - -* proche du code -* toujours Ă  jour -* structurĂ©e -* exploitable par des outils - -## 🎯 L’idĂ©e clĂ© - -> La documentation des erreurs n’est pas Ă©crite *Ă  propos* du systĂšme. -> Elle est dĂ©rivĂ©e *Ă  partir* du systĂšme. - -Le code est la source de vĂ©ritĂ©. +Le code reste la source de vĂ©ritĂ© ; le pipeline rend cette connaissance transportable. --- ---- +--- \ No newline at end of file diff --git a/doc/DocumentationExtractionReference.en.md b/doc/DocumentationExtractionReference.en.md new file mode 100644 index 0000000..a66c6b9 --- /dev/null +++ b/doc/DocumentationExtractionReference.en.md @@ -0,0 +1,235 @@ +# Extraction and Project Discovery Reference + +🌍 **Languages:** +🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./DocumentationExtractionReference.fr.md) + +This page is the operational reference for selecting projects and assemblies, running extraction workers, and handling failures. For the mental model first, read [Architecture of the Documentation Pipeline](ArchitectureOfTheDocumentationPipeline.en.md). + +## Solution mode + +The common CLI path starts from a solution: + +```bash +fce generate --solution ./MyApp.sln --format markdown --service-name my-api --output ./docs/errors +``` + +At a high level, solution mode: + +1. builds the whole solution unless `--no-build` is set; +2. lists projects through `dotnet sln list`; +3. selects projects according to the opt-in marker; +4. locates their output assemblies; +5. launches one extraction worker per assembly; +6. aggregates documentation and failures. + +The build runs on the solution itself, before project selection: a compile error in a project that never opted in still fails the run. + +## Project opt-in + +A project participates when its own `.csproj` contains: + +```xml + + true + +``` + +The marker is read directly from the project XML. It is not evaluated as a normal MSBuild property. + +| Declaration | Result | +| --- | --- | +| `true` once, unconditionally | project is included | +| absent | project is skipped | +| `false` | project is skipped | +| declared more than once | ambiguous and reported | +| declared under `Condition` | ambiguous and reported | + +Important consequences: + +- declaring the value only in `Directory.Build.props` does not opt the project in; +- importing the property from another file does not opt the project in; +- passing `-p:GenerateErrorDocumentation=true` to `dotnet build` does not opt the project in; +- the marker must be literal and unambiguous in the `.csproj` itself. + +Under a continue-on-failure policy (the default), ambiguous projects are reported and skipped. Under a strict policy (`--strict`), they fail generation. + +## Programmatic opt-in options + +`SolutionGenerationOptions` allows programmatic callers to change the defaults: + +- `OptInPropertyName` changes the marker name; +- `IncludeProjectsWithoutOptIn` includes projects without the marker. + +The `fce` CLI uses `GenerateErrorDocumentation` and the opt-in behavior described above. + +## Assembly mode + +Use pre-built assemblies when solution discovery or building should not be part of the run: + +```bash +fce generate \ + --assemblies ./artifacts/MyApp.Domain.dll \ + --assemblies ./artifacts/MyApp.Application.dll \ + --format json \ + --output ./artifacts/errors.json +``` + +`--assemblies` takes one path per occurrence; repeat the option for each assembly. + +Assembly mode documents exactly the binaries supplied. It does not apply the `.csproj` opt-in filter. + +Use it when: + +- another pipeline stage already built the application; +- assemblies come from different solutions; +- the caller needs exact binary selection; +- project discovery would be inappropriate. + +The caller remains responsible for providing compatible dependency files and runtime assets beside the target assembly (the build’s `.deps.json` and `runtimeconfig.json` plus the dependent assemblies). + +## Single-assembly extraction + +`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` performs in-process extraction for one already loaded assembly. + +It: + +- finds `[ProvidesErrorsFor]` classes; +- resolves methods referenced by `[DocumentedBy]`; +- invokes documentation methods and example factories; +- returns an `ErrorDocumentationExtractionResult` containing documentation and failures; +- deduplicates and orders documentation by error code. + +This low-level API is useful for controlled tooling and tests. Solution-level generation normally uses isolated workers instead. + +## Worker execution + +Each selected assembly is extracted in a short-lived worker process. The generator launches the worker with the target assembly's dependency context so its application dependencies and FirstClassErrors version resolve independently. + +The worker: + +1. loads the target assembly; +2. runs extraction; +3. serializes the complete extraction result as JSON; +4. exits. + +The generator reads that result and continues with the next assembly. + +## Why workers are required + +Documentation methods and example factories are executable code. They may: + +- initialize static state; +- load application dependencies; +- use a different FirstClassErrors version; +- throw during execution; +- crash the process; +- hang indefinitely. + +Per-assembly workers isolate those risks. A failure remains associated with the assembly that produced it. + +## Failures and continuation + +Extraction failures are data, not necessarily immediate process crashes. + +Failures reported by a worker that completes normally are always recorded and logged, and generation continues with the remaining assemblies regardless of the configured failure behavior: + +- a `[DocumentedBy]` target cannot be found; +- the target has an invalid signature; +- a documentation method throws; +- an example factory throws. + +Process-level failures honor the configured failure behavior, which determines whether the generator records the problem and continues with other assemblies, or treats the problem as fatal: + +- an assembly cannot be loaded; +- the worker exits unexpectedly; +- the worker exceeds its timeout. + +A continued run can therefore produce a partial catalog plus explicit failures. Consumers must not mistake “a file was generated” for “every assembly was documented successfully.” + +## Timeouts and process failures + +A worker that does not complete within its configured timeout is terminated and recorded as failed. A worker crash is also recorded with the available process information. + +When investigating a timeout: + +1. run the documented factory or example directly in a test; +2. check for blocking I/O, deadlocks, or environment-dependent initialization; +3. confirm the target's runtime and dependency files are available; +4. avoid network or production-service access in documentation factories; +5. make example factories small and deterministic. + +Documentation code should construct representative errors, not perform real application workflows. + +## Building and `--no-build` + +In solution mode, the generator builds the solution by default. Use `--no-build` only when the expected outputs already exist and match the current source. + +```bash +fce generate --solution ./MyApp.sln --no-build --format markdown --service-name my-api --output ./docs/errors +``` + +A safe CI sequence is: + +```bash +dotnet build MyApp.sln -c Release +fce generate --solution MyApp.sln --configuration Release --no-build --format markdown --service-name my-api --output artifacts/errors +``` + +If `--no-build` points to stale or missing outputs, extraction may document old code or fail to locate assemblies. + +## Configuration and framework selection + +The selected configuration and target framework must identify a real output for each participating project. Multi-targeted projects may require an explicit framework. + +Keep the CLI configuration aligned with the build that produced the assemblies: + +```bash +fce generate \ + --solution ./MyApp.sln \ + --configuration Release \ + --framework net8.0 \ + --no-build \ + --output ./artifacts/errors +``` + +## Failure-safe documentation factories + +A documentation method should be: + +- deterministic; +- fast; +- free of external I/O; +- independent of environment secrets; +- safe to execute repeatedly; +- limited to constructing documentation and representative errors. + +Avoid: + +- database calls; +- HTTP calls; +- reading mutable production configuration; +- reliance on current time or randomness when it affects output; +- starting background work; +- modifying global application state. + +## Troubleshooting checklist + +When expected errors are missing, verify: + +- the project has a literal `true` in its `.csproj`; +- the factory class has `[ProvidesErrorsFor]`; +- the factory has `[DocumentedBy]`; +- the referenced method exists and has a valid documentation-factory signature; +- the documentation method and example factories complete successfully; +- the intended configuration and framework were built; +- `--no-build` is not reusing stale outputs; +- worker failures and warnings were reviewed; +- assembly-mode paths point to the intended binaries. + +--- + + + +--- \ No newline at end of file diff --git a/doc/DocumentationExtractionReference.fr.md b/doc/DocumentationExtractionReference.fr.md new file mode 100644 index 0000000..4183587 --- /dev/null +++ b/doc/DocumentationExtractionReference.fr.md @@ -0,0 +1,235 @@ +# RĂ©fĂ©rence de l’extraction et de la dĂ©couverte des projets + +🌍 **Langues :** +🇬🇧 [English](./DocumentationExtractionReference.en.md) | đŸ‡«đŸ‡· Français (ce fichier) + +Cette page est la rĂ©fĂ©rence opĂ©rationnelle pour sĂ©lectionner les projets et assemblies, exĂ©cuter les workers d’extraction et traiter les Ă©checs. Pour commencer par le modĂšle mental, lisez [Architecture du pipeline de documentation](ArchitectureOfTheDocumentationPipeline.fr.md). + +## Mode solution + +Le parcours CLI courant part d’une solution : + +```bash +fce generate --solution ./MyApp.sln --format markdown --service-name my-api --output ./docs/errors +``` + +Le mode solution : + +1. compile toute la solution, sauf avec `--no-build` ; +2. liste les projets via `dotnet sln list` ; +3. les sĂ©lectionne selon le marqueur d’opt-in ; +4. localise leurs assemblies de sortie ; +5. lance un worker d’extraction par assembly ; +6. agrĂšge la documentation et les Ă©checs. + +Le build porte sur la solution elle-mĂȘme, avant la sĂ©lection des projets : une erreur de compilation dans un projet qui n’a jamais optĂ© fait quand mĂȘme Ă©chouer l’exĂ©cution. + +## Opt-in des projets + +Un projet participe lorsque son propre `.csproj` contient : + +```xml + + true + +``` + +Le marqueur est lu directement dans le XML du projet. Il n’est pas Ă©valuĂ© comme une propriĂ©tĂ© MSBuild normale. + +| DĂ©claration | RĂ©sultat | +| --- | --- | +| `true` une fois, sans condition | projet inclus | +| absente | projet ignorĂ© | +| `false` | projet ignorĂ© | +| dĂ©clarĂ©e plusieurs fois | ambiguĂŻtĂ© signalĂ©e | +| dĂ©clarĂ©e sous `Condition` | ambiguĂŻtĂ© signalĂ©e | + +ConsĂ©quences importantes : + +- une valeur uniquement dĂ©finie dans `Directory.Build.props` n’active pas le projet ; +- une propriĂ©tĂ© importĂ©e depuis un autre fichier n’active pas le projet ; +- `-p:GenerateErrorDocumentation=true` passĂ© Ă  `dotnet build` n’active pas le projet ; +- le marqueur doit ĂȘtre littĂ©ral et non ambigu dans le `.csproj` lui-mĂȘme. + +Avec une politique de poursuite (le comportement par dĂ©faut), les projets ambigus sont signalĂ©s puis ignorĂ©s. Avec une politique stricte (`--strict`), ils font Ă©chouer la gĂ©nĂ©ration. + +## Options d’opt-in programmatiques + +`SolutionGenerationOptions` permet de modifier les valeurs par dĂ©faut : + +- `OptInPropertyName` change le nom du marqueur ; +- `IncludeProjectsWithoutOptIn` inclut les projets sans marqueur. + +La CLI `fce` utilise `GenerateErrorDocumentation` et le comportement d’opt-in dĂ©crit ci-dessus. + +## Mode assemblies + +Utilisez des assemblies dĂ©jĂ  compilĂ©s lorsque la dĂ©couverte de solution ou le build ne doivent pas faire partie de l’exĂ©cution : + +```bash +fce generate \ + --assemblies ./artifacts/MyApp.Domain.dll \ + --assemblies ./artifacts/MyApp.Application.dll \ + --format json \ + --output ./artifacts/errors.json +``` + +`--assemblies` accepte un chemin par occurrence ; rĂ©pĂ©tez l’option pour chaque assembly. + +Le mode assemblies documente exactement les binaires fournis. Il n’applique pas le filtre d’opt-in du `.csproj`. + +Il est adaptĂ© lorsque : + +- une Ă©tape prĂ©cĂ©dente a dĂ©jĂ  compilĂ© l’application ; +- les assemblies proviennent de plusieurs solutions ; +- l’appelant veut sĂ©lectionner exactement les binaires ; +- la dĂ©couverte de projets n’est pas pertinente. + +L’appelant reste responsable de fournir les fichiers de dĂ©pendances et assets runtime compatibles Ă  cĂŽtĂ© de l’assembly cible (les `.deps.json` et `runtimeconfig.json` du build, plus les assemblies dĂ©pendants). + +## Extraction d’un assembly unique + +`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` effectue une extraction en processus pour un assembly dĂ©jĂ  chargĂ©. + +Cette API : + +- trouve les classes `[ProvidesErrorsFor]` ; +- rĂ©sout les mĂ©thodes rĂ©fĂ©rencĂ©es par `[DocumentedBy]` ; +- invoque les mĂ©thodes de documentation et factories d’exemples ; +- renvoie un `ErrorDocumentationExtractionResult` contenant documentation et Ă©checs ; +- dĂ©duplique et ordonne la documentation par code d’erreur. + +Elle est utile pour des outils contrĂŽlĂ©s et des tests. La gĂ©nĂ©ration au niveau solution utilise normalement des workers isolĂ©s. + +## ExĂ©cution des workers + +Chaque assembly sĂ©lectionnĂ© est extrait dans un processus worker Ă©phĂ©mĂšre. Le gĂ©nĂ©rateur le lance avec le contexte de dĂ©pendances de la cible afin d’isoler les dĂ©pendances applicatives et la version de FirstClassErrors. + +Le worker : + +1. charge l’assembly cible ; +2. exĂ©cute l’extraction ; +3. sĂ©rialise le rĂ©sultat complet en JSON ; +4. se termine. + +Le gĂ©nĂ©rateur lit ce rĂ©sultat puis passe Ă  l’assembly suivant. + +## Pourquoi les workers sont nĂ©cessaires + +Les mĂ©thodes de documentation et factories d’exemples sont du code exĂ©cutable. Elles peuvent : + +- initialiser de l’état statique ; +- charger des dĂ©pendances applicatives ; +- utiliser une autre version de FirstClassErrors ; +- lever une exception ; +- faire planter le processus ; +- rester bloquĂ©es. + +Les workers par assembly isolent ces risques et rattachent l’échec Ă  l’assembly qui l’a produit. + +## Échecs et poursuite + +Les Ă©checs d’extraction sont des donnĂ©es et ne provoquent pas nĂ©cessairement l’arrĂȘt immĂ©diat. + +Les Ă©checs rapportĂ©s par un worker qui se termine normalement sont toujours enregistrĂ©s et journalisĂ©s, et la gĂ©nĂ©ration poursuit avec les assemblies restants quelle que soit la politique d’échec configurĂ©e : + +- cible `[DocumentedBy]` introuvable ; +- signature invalide ; +- mĂ©thode de documentation qui lĂšve ; +- factory d’exemple qui lĂšve. + +Les Ă©checs de processus honorent la politique d’échec configurĂ©e, qui dĂ©cide si le gĂ©nĂ©rateur enregistre le problĂšme et poursuit avec les autres assemblies, ou le considĂšre comme fatal : + +- assembly impossible Ă  charger ; +- arrĂȘt inattendu du worker ; +- timeout du worker. + +Une exĂ©cution poursuivie peut donc produire un catalogue partiel avec des Ă©checs explicites. La prĂ©sence d’un fichier gĂ©nĂ©rĂ© ne prouve pas que tous les assemblies ont Ă©tĂ© documentĂ©s correctement. + +## Timeouts et crashs + +Un worker qui dĂ©passe son timeout est arrĂȘtĂ© et enregistrĂ© comme Ă©chouĂ©. Un crash est Ă©galement enregistrĂ© avec les informations disponibles. + +Pour analyser un timeout : + +1. exĂ©cutez directement la factory documentĂ©e ou l’exemple dans un test ; +2. cherchez les I/O bloquantes, deadlocks ou initialisations dĂ©pendantes de l’environnement ; +3. vĂ©rifiez la prĂ©sence des fichiers runtime et de dĂ©pendances ; +4. Ă©vitez tout accĂšs rĂ©seau ou service de production dans les factories de documentation ; +5. gardez les factories d’exemples petites et dĂ©terministes. + +Le code de documentation doit construire des erreurs reprĂ©sentatives, pas exĂ©cuter de vĂ©ritables workflows applicatifs. + +## Build et `--no-build` + +En mode solution, le gĂ©nĂ©rateur compile la solution par dĂ©faut. Utilisez `--no-build` uniquement lorsque les sorties attendues existent dĂ©jĂ  et correspondent au code courant. + +```bash +fce generate --solution ./MyApp.sln --no-build --format markdown --service-name my-api --output ./docs/errors +``` + +Une sĂ©quence CI sĂ»re est : + +```bash +dotnet build MyApp.sln -c Release +fce generate --solution MyApp.sln --configuration Release --no-build --format markdown --service-name my-api --output artifacts/errors +``` + +Des sorties obsolĂštes ou absentes peuvent documenter un ancien code ou provoquer un Ă©chec de localisation. + +## Configuration et framework + +La configuration et le framework sĂ©lectionnĂ©s doivent identifier une sortie rĂ©elle pour chaque projet participant. Les projets multi-cibles peuvent nĂ©cessiter un framework explicite. + +Gardez la configuration de la CLI alignĂ©e sur le build qui a produit les assemblies : + +```bash +fce generate \ + --solution ./MyApp.sln \ + --configuration Release \ + --framework net8.0 \ + --no-build \ + --output ./artifacts/errors +``` + +## Factories de documentation sĂ»res + +Une mĂ©thode de documentation doit ĂȘtre : + +- dĂ©terministe ; +- rapide ; +- sans I/O externe ; +- indĂ©pendante des secrets d’environnement ; +- sĂ»re Ă  exĂ©cuter plusieurs fois ; +- limitĂ©e Ă  la construction de documentation et d’erreurs reprĂ©sentatives. + +Évitez : + +- les appels de base de donnĂ©es ; +- les appels HTTP ; +- la lecture de configuration de production mutable ; +- la dĂ©pendance Ă  l’heure courante ou Ă  l’alĂ©atoire lorsqu’elle affecte la sortie ; +- le dĂ©marrage de tĂąches de fond ; +- la modification d’état global de l’application. + +## Checklist de dĂ©pannage + +Lorsque des erreurs attendues manquent, vĂ©rifiez : + +- la prĂ©sence du marqueur littĂ©ral `true` dans le `.csproj` du projet ; +- `[ProvidesErrorsFor]` sur la classe factory ; +- `[DocumentedBy]` sur la factory ; +- l’existence et la signature de la mĂ©thode rĂ©fĂ©rencĂ©e ; +- le succĂšs des mĂ©thodes de documentation et exemples ; +- la configuration et le framework compilĂ©s ; +- l’absence de sorties obsolĂštes avec `--no-build` ; +- les avertissements et Ă©checs des workers ; +- les chemins exacts en mode assemblies. + +--- + + + +--- \ No newline at end of file diff --git a/doc/Internationalisation.fr.md b/doc/Internationalisation.fr.md index f662249..ce32ddb 100644 --- a/doc/Internationalisation.fr.md +++ b/doc/Internationalisation.fr.md @@ -1,120 +1,250 @@ # Internationalisation -🌍 **Langues:** +🌍 **Langues :** 🇬🇧 [English](./Internationalization.en.md) | đŸ‡«đŸ‡· Français (ce fichier) -FirstClassErrors peut produire le catalogue d’erreurs en plusieurs langues. L’internationalisation est **optionnelle et granulaire** : sans aucune configuration, la documentation est en anglais, et vous ne localisez que ce que vous choisissez. +FirstClassErrors peut gĂ©nĂ©rer le mĂȘme catalogue d’erreurs dans plusieurs langues. L’internationalisation est optionnelle et granulaire : un projet peut tout localiser, ne localiser que certaines sources, ou ne rien localiser. -Deux choses peuvent ĂȘtre localisĂ©es, Ă  deux Ă©tapes diffĂ©rentes du pipeline : +La rĂšgle essentielle est que la localisation intervient Ă  **deux Ă©tapes distinctes du pipeline**. -| Quoi | LocalisĂ© quand | Comment | -| --- | --- | --- | -| **Contenu des erreurs** — titres, explications, rĂšgles, diagnostics, les messages publics (court et dĂ©taillĂ©), descriptions de source et de contexte | Ă  l’**extraction** | vos fabriques lisent des ressources localisĂ©es sous la culture UI courante | -| **Gabarits des renderers** — titres, libellĂ©s, en-tĂȘtes de tableau | au **rendu** | le renderer lit son propre texte fixe pour `RenderRequest.Culture` | +## Le modĂšle en un coup d’Ɠil + +```mermaid +flowchart LR + A[Langue demandĂ©e] + B[Culture d'extraction] + C[Contenu d'erreur localisĂ©] + D[Culture de rendu] + E[Gabarit de renderer localisĂ©] + F[Catalogue gĂ©nĂ©rĂ©] + + A --> B --> C + A --> D --> E + C --> F + E --> F +``` + +| Étape | Responsable de | +| --- | --- | +| extraction | titres, descriptions, rĂšgles, hypothĂšses de diagnostic, exemples, messages publics, descriptions de source et de clĂ©s de contexte | +| rendu | titres, libellĂ©s, en-tĂȘtes, navigation et autres textes fixes du renderer | + +Les identitĂ©s stables restent indĂ©pendantes de la culture. -Tout le reste demeure **indĂ©pendant de la culture**, pour que les liens ne cassent jamais d’une langue Ă  l’autre — et pour que les diagnostics restent dans une langue unique et cohĂ©rente pour les logs et le support : codes d’erreur, noms de source (`nameof(...)`), valeurs d’`ErrorOrigin`, le **message de diagnostic interne** de chaque erreur, ainsi que les noms de fichiers et les ancres gĂ©nĂ©rĂ©s. +## Ce qui reste invariant -### Les messages publics sont localisĂ©s, le message de diagnostic ne l’est pas +Ne localisez pas les valeurs utilisĂ©es comme contrats ou identifiants opĂ©rationnels : -Une erreur porte trois messages, qui se localisent diffĂ©remment : +- codes d’erreur ; +- noms de source créés avec `nameof(...)` ; +- valeurs d’`ErrorOrigin` ; +- noms de clĂ©s de contexte ; +- noms de fichiers et ancres gĂ©nĂ©rĂ©s ; +- noms de champs JSON et autres schĂ©mas machine ; +- `DiagnosticMessage`, le message runtime interne. -* **`ShortMessage`** et **`DetailedMessage`** sont du contenu public : ils sont localisĂ©s Ă  l’extraction comme n’importe quelle autre prose — lisez-les depuis des ressources sous la culture UI courante. -* **`DiagnosticMessage`** est dĂ©libĂ©rĂ©ment **conservĂ© dans la langue de l’auteur (indĂ©pendant de la culture)**. Il est destinĂ© aux logs, au support et aux dĂ©veloppeurs, et un texte de diagnostic est le plus utile lorsqu’il se lit toujours dans une langue unique et cohĂ©rente, quelle que soit la locale de l’appelant — c’est une bonne pratique assumĂ©e. +Ces valeurs stables garantissent que les liens, branches clientes, dashboards et requĂȘtes de logs fonctionnent dans toutes les langues du catalogue. -Ainsi, dans la documentation gĂ©nĂ©rĂ©e, les messages publics sont rendus localisĂ©s tandis que le message de diagnostic est rendu dans la langue invariante (celle de l’auteur). +## Les trois messages runtime -L’exemple `.Usage` fournit cinq langues — anglais, français, espagnol, allemand et suĂ©dois (`en`, `fr`, `es`, `de`, `sv`). +| Message | LocalisĂ© ? | Pourquoi | +| --- | --- | --- | +| `ShortMessage` | oui | message public pour utilisateurs ou clients d’API | +| `DetailedMessage` | oui | dĂ©tail public maĂźtrisĂ© | +| `DiagnosticMessage` | non | une langue interne cohĂ©rente pour les logs, le support et les dĂ©veloppeurs | + +Un catalogue français peut donc afficher des messages publics français tout en conservant un message de diagnostic anglais. C’est volontaire : le diagnostic explique une occurrence runtime Ă  un public interne commun. + +Pour les rĂšgles d’écriture, voir [Écrire les messages d’erreur](WritingErrorMessages.fr.md). ## Choisir la langue -Passez `--language` (alias `-l`) Ă  `fce generate`, ou dĂ©finissez une valeur `language` par dĂ©faut dans `fce.json` ; une valeur en ligne de commande Ă©crase la configuration, exactement comme les autres options. Le dĂ©faut est l’anglais. +Passez `--language` ou `-l` : ```bash -fce generate --solution ./MyApp.sln --format markdown --language sv --output ./docs/errors +fce generate \ + --solution ./MyApp.sln \ + --format markdown \ + --language fr \ + --service-name my-api \ + --output ./docs/errors-fr ``` +Ou configurez une valeur par dĂ©faut dans `fce.json` : + ```json { "solution": "./MyApp.sln", - "language": "sv" + "language": "fr" } ``` -## Niveau 1 — localiser le contenu des erreurs +Une valeur en ligne de commande Ă©crase la configuration. Sans option de langue, la langue par dĂ©faut du catalogue est l’anglais. + +## Localiser le contenu documentaire -Le contenu des erreurs est localisĂ© Ă  l’**extraction**. Le gĂ©nĂ©rateur lance le worker de chaque assembly avec `CultureInfo.CurrentUICulture` rĂ©glĂ© sur la langue demandĂ©e, de sorte que toute fabrique qui lit des ressources localisĂ©es produit cette langue. Dans l’exemple, la prose est lue depuis un petit wrapper de `ResourceManager` (`UsageErrorMessages`) adossĂ© Ă  un `.resx` par langue : +Pendant l’extraction, le processus worker d’extraction (voir [Architecture](ArchitectureOfTheDocumentationPipeline.fr.md)) utilise `CultureInfo.CurrentUICulture` avec la langue demandĂ©e. Les mĂ©thodes de documentation et factories peuvent donc lire normalement des ressources `.resx`. ```csharp private static ErrorDocumentation BelowAbsoluteZeroDocumentation() { - return DescribeError.WithTitle(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Title")) - .WithDescription(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Description")) - // 
rĂšgles, diagnostics, exemples lus de la mĂȘme façon - ; + return DescribeError + .WithTitle(Messages.Get("Temperature_BelowAbsoluteZero_Title")) + .WithDescription(Messages.Get("Temperature_BelowAbsoluteZero_Description")) + .WithRule(Messages.Get("Temperature_BelowAbsoluteZero_Rule")) + .WithDiagnostic( + Messages.Get("Temperature_BelowAbsoluteZero_Cause"), + ErrorOrigin.Internal, + Messages.Get("Temperature_BelowAbsoluteZero_Hint")) + .WithExamples(() => BelowAbsoluteZero(-1m)); } ``` -Vous ĂȘtes libre d’écrire des chaĂźnes littĂ©rales Ă  la place — l’erreur est alors simplement toujours dans cette langue (voir [Opt-in et localisation partielle](#opt-in-et-localisation-partielle)). +La factory runtime peut localiser ses messages publics depuis la mĂȘme source : + +```csharp +return DomainError.Create( + Code.BelowAbsoluteZero, + diagnosticMessage: $"Temperature {value} is below absolute zero.") + .WithPublicMessage( + shortMessage: Messages.Get("Temperature_BelowAbsoluteZero_ShortMessage"), + detailedMessage: Messages.Get("Temperature_BelowAbsoluteZero_DetailedMessage")); +``` + +Le message de diagnostic reste rĂ©digĂ© directement dans la langue interne choisie par le projet. -### La description du groupe de source +## Localiser les descriptions de source -`[ProvidesErrorsFor]` accepte un `DescriptionResourceType`. Lorsqu’il est renseignĂ©, l’extracteur traite `Description` comme une **clĂ© de ressource** rĂ©solue via ce type — le mĂȘme patron que `[Display(ResourceType = 
)]` de DataAnnotations. En son absence, `Description` est un texte littĂ©ral. +`[ProvidesErrorsFor]` peut traiter `Description` comme une clĂ© de ressource lorsque `DescriptionResourceType` est renseignĂ© : ```csharp -[ProvidesErrorsFor(nameof(Amount), - Description = "Amount_Source", // une clĂ© de ressource
 - DescriptionResourceType = typeof(UsageErrorMessages))] // 
rĂ©solue via ces ressources +[ProvidesErrorsFor( + nameof(Amount), + Description = "Amount_Source_Description", + DescriptionResourceType = typeof(Messages))] +public static class InvalidAmountError { +} ``` -### Les descriptions de clĂ©s de contexte +Sans `DescriptionResourceType`, la description est littĂ©rale et reste dans sa langue d’écriture. + +## Localiser les descriptions de clĂ©s de contexte -Une `ErrorContextKey` est enregistrĂ©e une seule fois par son nom, mais sa description peut ĂȘtre rĂ©solue paresseusement pour suivre la culture courante. Utilisez la surcharge `Func` de `Create` : +Le nom de clĂ© reste stable, mais sa description documentaire peut ĂȘtre rĂ©solue paresseusement : ```csharp public static readonly ErrorContextKey TransactionDate = - ErrorContextKey.Create("TRANSACTION_DATE", () => UsageErrorMessages.Get("Bank_TransactionDate_Context")); + ErrorContextKey.Create( + "TRANSACTION_DATE", + () => Messages.Get("TransactionDate_Context_Description")); +``` + +On obtient ainsi une prose localisĂ©e dans le catalogue sans modifier la clĂ© opĂ©rationnelle utilisĂ©e dans les logs. + +## Localiser les gabarits des renderers + +Un renderer reçoit la culture cible via `RenderRequest.Culture`. Il doit l’utiliser uniquement pour le texte qui lui appartient : + +```csharp +// RendererResources est votre propre classe de ressources .resx, pas un type de la bibliothĂšque. +string heading = RendererResources.GetString( + "ErrorCatalogHeading", + request.Culture) ?? "Error catalog"; ``` -L’identitĂ© de la clĂ© (son nom) reste figĂ©e ; seul le texte de la description est diffĂ©rĂ© et lu sous la culture en vigueur au moment de l’extraction. +Le catalogue reçu contient dĂ©jĂ  le contenu d’erreur localisĂ©. Le traduire une seconde fois mĂ©langerait les responsabilitĂ©s et pourrait produire une sortie incohĂ©rente. -## Niveau 2 — localiser les gabarits des renderers +Le renderer JSON garde ses noms de champs invariants : il s’agit d’un contrat machine, pas de prose utilisateur. -Le texte fixe propre Ă  un renderer (titres, libellĂ©s, en-tĂȘtes de tableau) est localisĂ© au **rendu**, depuis `RenderRequest.Culture`. Le renderer Markdown intĂ©grĂ© lit ses chaĂźnes depuis un jeu de `.resx` pour cette culture ; le renderer JSON n’a aucun texte fixe Ă  traduire — ses noms de champs sont un schĂ©ma machine, pas de la prose. +Voir [Écrire son propre renderer](WritingACustomRenderer.fr.md). -Un renderer personnalisĂ© localise son gabarit de la mĂȘme façon — voir [Écrire son propre renderer](WritingACustomRenderer.fr.md). Le *contenu* des erreurs qu’il reçoit est dĂ©jĂ  localisĂ© en amont : un renderer ne localise donc jamais que son propre texte. +## La localisation partielle est valide -## Opt-in et localisation partielle +L’internationalisation n’est pas tout ou rien. -L’internationalisation n’est jamais imposĂ©e : +Un projet peut contenir : -* Une erreur dont le `[ProvidesErrorsFor]` n’a pas de `DescriptionResourceType` conserve sa `Description` littĂ©rale. -* Une fabrique qui Ă©crit des chaĂźnes en dur (plutĂŽt que de lire des ressources) reste toujours dans cette langue. -* Sans `--language`, tout est rendu en anglais (la culture invariante), Ă  l’octet prĂšs comme avant l’existence de l’i18n. +- des sources entiĂšrement localisĂ©es ; +- de la documentation rĂ©digĂ©e comme littĂ©raux anglais ; +- des descriptions de source issues de ressources avec des diagnostics fixes ; +- des renderers traduits dans moins de langues que le contenu applicatif. -Un projet ne s’internationalise donc que lĂ  oĂč il le souhaite. L’exemple `.Usage` montre les deux extrĂȘmes : `Temperature` est un exemple simple, non localisĂ©, tandis qu’`Amount` et `BankTransactionFileValidator` sont entiĂšrement localisĂ©s dans les cinq langues. +Lorsqu’une ressource manque, le fallback appartient Ă  la stratĂ©gie de ressources de l’application. VĂ©rifiez qu’une ressource absente ne produit pas silencieusement des titres, rĂšgles ou messages publics vides. -## L’utiliser sans la CLI +## GĂ©nĂ©rer plusieurs langues en CI -Lorsque vous pilotez le pipeline vous-mĂȘme, rĂ©glez la **mĂȘme** culture sur les deux Ă©tapes pour que le contenu et les gabarits concordent : +ExĂ©cutez une gĂ©nĂ©ration par langue et publiez des dossiers distincts : + +```bash +fce generate --solution MyApp.sln --no-build \ + --format markdown --language en --service-name my-api \ + --output artifacts/errors/en + +fce generate --solution MyApp.sln --no-build \ + --format markdown --language fr --service-name my-api \ + --output artifacts/errors/fr +``` + +Publiez ensemble les langues d’une mĂȘme version afin que le support puisse changer de langue sans ouvrir la documentation d’un autre dĂ©ploiement. + +Les noms de fichiers et ancres restent invariants, ce qui rend les liens inter-langues prĂ©visibles. + +## Utiliser le pipeline programmatiquement + +Utilisez la mĂȘme culture pour l’extraction et le rendu : ```csharp -CultureInfo culture = CultureInfo.GetCultureInfo("sv"); +CultureInfo culture = CultureInfo.GetCultureInfo("fr"); IEnumerable catalog = SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom( - "MyApp.sln", new SolutionGenerationOptions { Culture = culture }); + "MyApp.sln", + new SolutionGenerationOptions { Culture = culture }); + +RenderRequest request = new(RenderLayouts.Single, culture, "my-api"); -RenderRequest request = new(RenderLayouts.Single, culture); -IReadOnlyList documents = new MarkdownErrorDocumentationRenderer().Render(catalog, request); +IReadOnlyList documents = + new MarkdownErrorDocumentationRenderer().Render(catalog, request); ``` -## Comment la culture traverse le pipeline +Le nom de service est exigĂ© par les renderers markdown et html, qui intĂšgrent des exemples RFC 9457 (« Problem Details » pour les API HTTP) typĂ©s `urn:problem:{service}:{code}` ; le format json accepte `null`. -| Étape | Source de la culture | Ce qu’elle localise | -| --- | --- | --- | -| Worker / extraction | `CultureInfo.CurrentUICulture` (rĂ©glĂ©e depuis `--language`) | le contenu des erreurs (titres, explications, rĂšgles, diagnostics, les messages publics court et dĂ©taillĂ©, descriptions de source et de contexte) | -| Renderer | `RenderRequest.Culture` | le texte fixe propre au renderer (titres, libellĂ©s, en-tĂȘtes de tableau) | +Employer volontairement deux cultures diffĂ©rentes produit une sortie multilingue mĂ©langĂ©e et devrait rester exceptionnel. + +## Erreurs frĂ©quentes + +### Localiser les codes ou noms de clĂ©s + +Cela casse clients, dashboards et liens. Localisez les descriptions, jamais les identitĂ©s. -Le contenu est localisĂ© Ă  l’extraction ; le texte fixe au rendu. Les noms de fichiers, les ancres et le message de diagnostic interne de chaque erreur restent indĂ©pendants de la culture. +### Localiser `DiagnosticMessage` selon l’appelant + +Les logs d’un mĂȘme type d’erreur deviennent dĂ©pendants de la langue et plus difficiles Ă  rechercher. Gardez une langue interne unique. + +### Traduire le contenu applicatif dans un renderer + +Le renderer reçoit dĂ©jĂ  le contenu localisĂ©. Il possĂšde uniquement son gabarit. + +### Croire qu’un littĂ©ral est traduit automatiquement + +Un littĂ©ral reste littĂ©ral. Utilisez des ressources lorsque la localisation est nĂ©cessaire. + +### Publier des langues provenant de builds diffĂ©rents + +Les catalogues peuvent dĂ©crire des codes diffĂ©rents. GĂ©nĂ©rez toutes les langues depuis les mĂȘmes binaires et la mĂȘme version. + +## Checklist de revue + +Avant de publier des catalogues localisĂ©s, vĂ©rifiez que : + +- la prose publique utilise les ressources prĂ©vues ; +- codes, clĂ©s, chemins, ancres et schĂ©mas restent invariants ; +- `DiagnosticMessage` reste dans la langue interne choisie ; +- les descriptions de source et de contexte suivent la culture demandĂ©e ; +- les libellĂ©s de renderer utilisent `RenderRequest.Culture` ; +- le fallback de ressources est explicite et testĂ© ; +- toutes les langues proviennent des mĂȘmes binaires ; +- les dossiers de langue sont versionnĂ©s et publiĂ©s ensemble ; +- les parcours CLI et programmatiques utilisent la mĂȘme culture aux deux Ă©tapes. --- @@ -122,4 +252,4 @@ Le contenu est localisĂ© Ă  l’extraction ; le texte fixe au rendu. Les noms de ← Écrire son propre renderer · ↑ Table des matiĂšres · FAQ → ---- +--- \ No newline at end of file diff --git a/doc/Internationalization.en.md b/doc/Internationalization.en.md index b8a1e59..6c4d94c 100644 --- a/doc/Internationalization.en.md +++ b/doc/Internationalization.en.md @@ -3,118 +3,248 @@ 🌍 **Languages:** 🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./Internationalisation.fr.md) -FirstClassErrors can produce the error catalog in several languages. Internationalization is **opt-in and granular**: with no setup the documentation is English, and you localize only what you choose. +FirstClassErrors can generate the same error catalog in several languages. Internationalization is optional and granular: a project may localize every documented error, only selected sources, or nothing at all. -Two things can be localized, at two different stages of the pipeline: +The most important rule is that localization happens at **two different pipeline stages**. -| What | Localized when | How | -| --- | --- | --- | -| **Error content** — titles, explanations, rules, diagnostics, the public messages (short and detailed), source and context descriptions | at **extraction** | your factories read localized resources under the current UI culture | -| **Renderer templates** — headings, labels, table headers | at **rendering** | the renderer reads its own boilerplate for `RenderRequest.Culture` | +## The model at a glance + +```mermaid +flowchart LR + A[Requested language] + B[Extraction culture] + C[Localized error content] + D[Render culture] + E[Localized renderer template] + F[Generated catalog] + + A --> B --> C + A --> D --> E + C --> F + E --> F +``` + +| Stage | Owns | +| --- | --- | +| extraction | titles, descriptions, rules, diagnostic hypotheses, examples, public messages, source descriptions, context-key descriptions | +| rendering | headings, labels, table headers, navigation, and other renderer-owned boilerplate | + +Stable identities remain culture-invariant. -Everything else stays **culture-invariant**, so links never break across languages — and so diagnostics stay in one consistent language for logs and support: error codes, source names (`nameof(...)`), `ErrorOrigin` values, the **internal diagnostic message** of each error, and the generated file names and anchors. +## What remains invariant -### Public messages are localized, the diagnostic message is not +Do not localize values used as contracts or operational identifiers: -An error carries three messages, and they localize differently: +- error codes; +- source names created with `nameof(...)`; +- `ErrorOrigin` values; +- context-key names; +- generated file names and anchors; +- JSON field names and other machine schemas; +- `DiagnosticMessage`, the internal runtime message. -* **`ShortMessage`** and **`DetailedMessage`** are public content, so they are localized at extraction like any other prose — read them from resources under the current UI culture. -* **`DiagnosticMessage`** is deliberately **kept in the author language (culture-invariant)**. It is meant for logs, support and developers, and diagnostic text is most useful when it always reads in one consistent language, regardless of the caller's locale — a deliberate best practice. +Keeping these values stable ensures that links, client branches, dashboards, and log queries work across every catalog language. -As a result, in the generated documentation the public messages render localized while the diagnostic message renders in the invariant (author) language. +## The three runtime messages -The `.Usage` sample ships five languages — English, French, Spanish, German and Swedish (`en`, `fr`, `es`, `de`, `sv`). +| Message | Localized? | Reason | +| --- | --- | --- | +| `ShortMessage` | yes | public message for users or API clients | +| `DetailedMessage` | yes | controlled public detail | +| `DiagnosticMessage` | no | one consistent internal language for logs, support, and development | + +A French catalog may therefore show French public messages while preserving an English diagnostic message. This is intentional: the diagnostic message identifies and explains one runtime occurrence for the internal audience. + +For message-writing rules, see [Writing Error Messages](WritingErrorMessages.en.md). -## Choosing the language +## Choose the language -Pass `--language` (alias `-l`) to `fce generate`, or set a `language` default in `fce.json`; a command-line value overrides the configuration, exactly like the other options. The default is English. +Pass `--language` or `-l`: ```bash -fce generate --solution ./MyApp.sln --format markdown --language sv --output ./docs/errors +fce generate \ + --solution ./MyApp.sln \ + --format markdown \ + --language fr \ + --service-name my-api \ + --output ./docs/errors-fr ``` +Or configure a default in `fce.json`: + ```json { "solution": "./MyApp.sln", - "language": "sv" + "language": "fr" } ``` -## Level 1 — localizing the error content +A command-line value overrides the configuration. Without a language option, the default catalog language is English. + +## Localize documentation content -Error content is localized at **extraction time**. The generator runs each assembly's worker with `CultureInfo.CurrentUICulture` set to the requested language, so any factory that reads localized resources produces that language. In the sample, the prose is read from a small `ResourceManager` wrapper (`UsageErrorMessages`) backed by a `.resx` per language: +During extraction, the extraction worker process (see [Architecture](ArchitectureOfTheDocumentationPipeline.en.md)) runs with `CultureInfo.CurrentUICulture` set from the requested language. Documentation methods and error factories can therefore read `.resx` resources normally. ```csharp private static ErrorDocumentation BelowAbsoluteZeroDocumentation() { - return DescribeError.WithTitle(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Title")) - .WithDescription(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Description")) - // 
rules, diagnostics, examples read the same way - ; + return DescribeError + .WithTitle(Messages.Get("Temperature_BelowAbsoluteZero_Title")) + .WithDescription(Messages.Get("Temperature_BelowAbsoluteZero_Description")) + .WithRule(Messages.Get("Temperature_BelowAbsoluteZero_Rule")) + .WithDiagnostic( + Messages.Get("Temperature_BelowAbsoluteZero_Cause"), + ErrorOrigin.Internal, + Messages.Get("Temperature_BelowAbsoluteZero_Hint")) + .WithExamples(() => BelowAbsoluteZero(-1m)); } ``` -You are free to author plain string literals instead — that error is then simply always in that one language (see [Opt-in and partial localization](#opt-in-and-partial-localization)). +The runtime factory can localize its public messages through the same resource source: + +```csharp +return DomainError.Create( + Code.BelowAbsoluteZero, + diagnosticMessage: $"Temperature {value} is below absolute zero.") + .WithPublicMessage( + shortMessage: Messages.Get("Temperature_BelowAbsoluteZero_ShortMessage"), + detailedMessage: Messages.Get("Temperature_BelowAbsoluteZero_DetailedMessage")); +``` + +The diagnostic message remains authored directly in the project’s chosen internal language. -### The source-group description +## Localize source descriptions -`[ProvidesErrorsFor]` accepts a `DescriptionResourceType`. When it is set, the extractor treats `Description` as a **resource key** resolved against that type — the same pattern as `[Display(ResourceType = 
)]` in DataAnnotations. When it is absent, `Description` is literal text. +`[ProvidesErrorsFor]` can treat `Description` as a resource key when `DescriptionResourceType` is set: ```csharp -[ProvidesErrorsFor(nameof(Amount), - Description = "Amount_Source", // a resource key
 - DescriptionResourceType = typeof(UsageErrorMessages))] // 
resolved against these resources +[ProvidesErrorsFor( + nameof(Amount), + Description = "Amount_Source_Description", + DescriptionResourceType = typeof(Messages))] +public static class InvalidAmountError { +} ``` -### Context-key descriptions +Without `DescriptionResourceType`, the description is literal and remains in its authored language. + +## Localize context-key descriptions -An `ErrorContextKey` is registered once by its name, but its description can be resolved lazily so it follows the current culture. Use the `Func` overload of `Create`: +The key name remains stable, but its documentation description can be resolved lazily: ```csharp public static readonly ErrorContextKey TransactionDate = - ErrorContextKey.Create("TRANSACTION_DATE", () => UsageErrorMessages.Get("Bank_TransactionDate_Context")); + ErrorContextKey.Create( + "TRANSACTION_DATE", + () => Messages.Get("TransactionDate_Context_Description")); +``` + +This produces localized catalog prose without changing the operational key used in logs. + +## Localize renderer templates + +A renderer receives the target culture through `RenderRequest.Culture`. It should use that culture only for text that belongs to the renderer: + +```csharp +// RendererResources is your own .resx-backed resource class, not a library type. +string heading = RendererResources.GetString( + "ErrorCatalogHeading", + request.Culture) ?? "Error catalog"; ``` -The key's identity (its name) stays fixed; only the description text is deferred and read under the culture in effect when the documentation is extracted. +The catalog passed to the renderer already contains localized error content. Translating it again would mix responsibilities and can produce inconsistent output. -## Level 2 — localizing the renderer templates +The JSON renderer keeps its schema field names invariant because they are a machine contract, not user-facing prose. -A renderer's own boilerplate (headings, labels, table headers) is localized at **rendering time**, from `RenderRequest.Culture`. The built-in Markdown renderer reads its strings from a `.resx` set for that culture; the JSON renderer has no boilerplate to translate — its field names are a machine schema, not prose. +See [Writing a custom renderer](WritingACustomRenderer.en.md). -A custom renderer localizes its template the same way — see [Writing a custom renderer](WritingACustomRenderer.en.md). The error *content* it receives is already localized upstream, so a renderer only ever localizes its own text. +## Partial localization is valid -## Opt-in and partial localization +Internationalization is not all-or-nothing. -Internationalization is never forced: +A project may contain: -* An error whose `[ProvidesErrorsFor]` has no `DescriptionResourceType` keeps its literal `Description`. -* A factory that authors plain strings (rather than reading resources) is always in that one language. -* Without `--language`, everything renders in English (the invariant culture), byte-for-byte as before i18n existed. +- fully localized sources; +- documentation written as English literals; +- source descriptions backed by resources but fixed diagnostic messages; +- custom renderers localized in fewer languages than the application content. -So a project internationalizes only where it wants to. The `.Usage` sample shows both ends: `Temperature` is a plain, non-localized example, while `Amount` and `BankTransactionFileValidator` are fully localized across the five languages. +When a resource is unavailable, the fallback behavior belongs to the application’s resource strategy. Ensure that missing resources do not silently produce empty titles, rules, or public messages. -## Using it without the CLI +## Generate several languages in CI -When you drive the pipeline yourself, set the **same** culture on both stages so the content and the templates match: +Run one generation per language and publish separate directories: + +```bash +fce generate --solution MyApp.sln --no-build \ + --format markdown --language en --service-name my-api \ + --output artifacts/errors/en + +fce generate --solution MyApp.sln --no-build \ + --format markdown --language fr --service-name my-api \ + --output artifacts/errors/fr +``` + +Keep identical catalog versions together so support can switch language without opening documentation from another deployment. + +File names and anchors remain invariant, which makes language switching and cross-language links predictable. + +## Use the pipeline programmatically + +Set the same culture for extraction and rendering: ```csharp -CultureInfo culture = CultureInfo.GetCultureInfo("sv"); +CultureInfo culture = CultureInfo.GetCultureInfo("fr"); IEnumerable catalog = SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom( - "MyApp.sln", new SolutionGenerationOptions { Culture = culture }); + "MyApp.sln", + new SolutionGenerationOptions { Culture = culture }); + +RenderRequest request = new(RenderLayouts.Single, culture, "my-api"); -RenderRequest request = new(RenderLayouts.Single, culture); -IReadOnlyList documents = new MarkdownErrorDocumentationRenderer().Render(catalog, request); +IReadOnlyList documents = + new MarkdownErrorDocumentationRenderer().Render(catalog, request); ``` -## How the culture flows through the pipeline +The service name is required by the markdown and html renderers, which embed RFC 9457 (Problem Details for HTTP APIs) examples typed `urn:problem:{service}:{code}`; the json format accepts `null`. -| Stage | Culture source | What it localizes | -| --- | --- | --- | -| Worker / extraction | `CultureInfo.CurrentUICulture` (set from `--language`) | error content (titles, explanations, rules, diagnostics, the public short and detailed messages, source and context descriptions) | -| Renderer | `RenderRequest.Culture` | the renderer's own templates (headings, labels, table headers) | +Using different cultures intentionally produces mixed-language output and should be rare. + +## Common mistakes + +### Localizing codes or key names + +This breaks clients, dashboards, and links. Localize descriptions, never identities. -Content is localized at extraction; boilerplate at rendering. File names, anchors, and each error's internal diagnostic message stay culture-invariant. +### Localizing `DiagnosticMessage` per caller + +Logs for the same error type become language-dependent and harder to search. Keep one internal author language. + +### Translating application content inside a renderer + +The renderer receives already localized content. It owns only its template. + +### Treating literal text as automatically translated + +A literal remains literal. Use resource-backed values where localization is required. + +### Publishing languages from different builds + +The catalogs may describe different code. Generate every language from the same build and version. + +## Review checklist + +Before publishing localized catalogs, verify that: + +- public prose is backed by the intended resources; +- codes, keys, paths, anchors, and schemas stay invariant; +- `DiagnosticMessage` stays in the chosen internal language; +- source and context descriptions resolve in the requested culture; +- renderer labels use `RenderRequest.Culture`; +- missing-resource fallback is explicit and tested; +- every language is generated from the same binaries; +- language directories are versioned and published together; +- the CLI and programmatic paths use the same culture for extraction and rendering. --- @@ -122,4 +252,4 @@ Content is localized at extraction; boilerplate at rendering. File names, anchor ← Writing a custom renderer · ↑ Table of contents · FAQ → ---- +--- \ No newline at end of file diff --git a/doc/WritingACustomRenderer.en.md b/doc/WritingACustomRenderer.en.md index b54ff6e..c83d901 100644 --- a/doc/WritingACustomRenderer.en.md +++ b/doc/WritingACustomRenderer.en.md @@ -3,37 +3,100 @@ 🌍 **Languages:** 🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./WritingACustomRenderer.fr.md) -The built-in `json` and `markdown` formats cover the common cases, but any output format — HTML, CSV, a company documentation template — can be added as a **custom renderer**. A renderer depends only on the documentation model, not on how the catalog was produced, so writing one is small and self-contained. +A custom renderer turns the extracted `ErrorDocumentation` catalog into a format owned by your organization: CSV, a documentation portal payload, a company-specific static site, or another machine-readable representation. -## The contract +A renderer does **not** discover projects or execute error factories. It receives an already extracted catalog and decides only how to present it. + +## The workflow + +```mermaid +flowchart LR + A[Extracted ErrorDocumentation catalog] + B[Custom renderer] + C[One or more RenderedDocument values] + D[Files written by CLI or application] + + A --> B --> C --> D +``` + +To add a format: + +1. reference `FirstClassErrors`; +2. implement `IErrorDocumentationRenderer`; +3. declare a unique `Format` and supported layouts; +4. return one or more `RenderedDocument` values; +5. package the renderer in a loadable assembly; +6. register that assembly in `fce.json` or with the CLI. -A renderer implements `IErrorDocumentationRenderer` (shipped in the `FirstClassErrors` package, namespace `FirstClassErrors.GenDoc.Rendering`): +## The contract ```csharp public interface IErrorDocumentationRenderer { - // The value selected with `fce generate --format <
>`. string Format { get; } - - // The layouts this renderer can produce, e.g. "single", "split" (see RenderLayouts). IReadOnlyCollection SupportedLayouts { get; } + IReadOnlyList Render( + IEnumerable catalog, + RenderRequest request); +} +``` + +The renderer contract types live in the `FirstClassErrors.GenDoc.Rendering` namespace of the `FirstClassErrors` package; the documentation model itself (`ErrorDocumentation` and related types) lives in the `FirstClassErrors` namespace. + +### `Format` + +`Format` is the value selected by `--format`: - // Turn the catalog into one or more output files for the requested layout and culture. - IReadOnlyList Render(IEnumerable catalog, RenderRequest request); +```csharp +public string Format => "csv"; +``` + +Choose a stable, lowercase name. Built-in formats take precedence if a custom renderer reuses `json`, `markdown`, or `html`. + +### `SupportedLayouts` + +A layout describes the shape of the output, not its format: + +```csharp +public IReadOnlyCollection SupportedLayouts { get; } = + new[] { RenderLayouts.Single }; +``` + +The built-in names are `single` and `split`, but a custom renderer may define another string when its output model requires it. + +Reject an unsupported request explicitly: + +```csharp +if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) { + throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts); } ``` -`RenderedDocument` is a `(RelativePath, Content)` pair. Return a single document for a one-file format, or several (an index plus one file per error) for a multi-file one — the `RelativePath` is used as the file name when the output target is a directory. +### `RenderedDocument` + +Each returned document contains: + +- `RelativePath`, the path below the selected output location; +- `Content`, the complete file contents. -`RenderRequest` carries the two per-call choices: +Return one document for a single-file format. Return several documents for a site or split layout. -* **`Layout`** — the value of `fce generate --layout <
>`. Declare the layouts you support in `SupportedLayouts` and reject any other with `LayoutNotSupportedException` (the built-in `json` renderer supports only `single`; `markdown` supports `single` and `split`). A layout is a free-form string, so a renderer may define its own. -* **`Culture`** — the target language. Localize any boilerplate you emit for `request.Culture` (the error *content* is already localized upstream by the extractor, so a renderer only localizes its own template text). See [Internationalization](Internationalization.en.md). +Keep paths relative, deterministic, and safe. Do not write files directly inside `Render(...)`; the caller owns the output destination. -The contract and the model (`ErrorDocumentation`, `ErrorDiagnostic`, 
) ship in the `FirstClassErrors` package, which targets **.NET Standard 2.0** — so a renderer needs only that one reference, which most projects already have. +### `RenderRequest` -## A minimal example +`RenderRequest` carries: + +- `Layout`, selected by `--layout`; +- `Culture`, used for renderer-owned headings, labels, and template text; +- `ServiceName`, set from `--service-name` or the configuration, used by renderers that emit RFC 9457 (Problem Details for HTTP APIs) problem types (`urn:problem:{service}:{code}`); `null` when none is configured. + +The catalog content has already been localized during extraction. A renderer must not reinterpret or retranslate error titles, rules, messages, or diagnostics. + +## Complete minimal CSV renderer ```csharp +using System; +using System.Collections.Generic; using System.Linq; using FirstClassErrors; @@ -43,36 +106,46 @@ public sealed class CsvErrorDocumentationRenderer : IErrorDocumentationRenderer public string Format => "csv"; - // A single CSV file — this renderer supports only the "single" layout. - public IReadOnlyCollection SupportedLayouts { get; } = new[] { RenderLayouts.Single }; + public IReadOnlyCollection SupportedLayouts { get; } = + new[] { RenderLayouts.Single }; + + public IReadOnlyList Render( + IEnumerable catalog, + RenderRequest request) { - public IReadOnlyList Render(IEnumerable catalog, RenderRequest request) { if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) { throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts); } - var rows = catalog.Select(error => $"{error.Code},{Quote(error.Title)}"); - var content = "code,title\n" + string.Join("\n", rows); + IEnumerable rows = catalog.Select(error => + $"{Quote(error.Code.ToString())},{Quote(error.Title)}"); - return new[] { new RenderedDocument("errors.csv", content) }; + string content = "code,title\n" + string.Join("\n", rows); + + return new[] { + new RenderedDocument("errors.csv", content) + }; } - private static string Quote(string? value) => $"\"{(value ?? string.Empty).Replace("\"", "\"\"")}\""; + private static string Quote(string? value) { + string escaped = (value ?? string.Empty).Replace("\"", "\"\""); + return $"\"{escaped}\""; + } } ``` -That is a complete renderer. (This CSV has no boilerplate to translate; a renderer that emits headings or labels would read them from resources keyed by `request.Culture`.) +This renderer is deterministic, supports one layout, and returns one complete file. -## Plugging it into the CLI +## Register it with the CLI -Build your renderer into a library, then register it: +Build the renderer into a class library, then register the assembly: ```bash fce config renderer add ./plugins/MyCompany.Renderers.dll -fce generate --solution MyApp.sln --format csv --output errors.csv +fce config renderer list ``` -`fce config renderer add` records the library path in `fce.json` (you can also edit the file by hand). At generation time the CLI loads the referenced assemblies, discovers every public renderer with a parameterless constructor, and selects the one whose `Format` matches `--format`. `fce config renderer list` shows the built-in and configured formats, and an unknown `--format` lists what is available. +The configuration contains the registered path: ```json { @@ -80,38 +153,94 @@ fce generate --solution MyApp.sln --format csv --output errors.csv } ``` -Paths are absolute or relative to `fce.json`, so a configuration is portable with its plugins. +Paths may be absolute or relative to `fce.json`. Relative paths make a repository-local configuration portable. + +Generate with the new format: + +```bash +fce generate \ + --solution ./MyApp.sln \ + --format csv \ + --layout single \ + --output ./artifacts/errors.csv +``` + +## Discovery and loading rules -### Things to know +The CLI discovers public renderer types in configured assemblies. A renderer must: -* **Parameterless constructor** — the CLI instantiates renderers by reflection. -* **Shared contract** — reference `FirstClassErrors`, but do not ship your own copy of it next to the CLI: the renderer type must resolve to the CLI's contract assembly. Reference it without copying (e.g. `false` on the reference), or rely on the identical version already sitting beside the CLI. -* **Target framework** — the CLI loads the plugin into its own process, so build it for a framework the CLI can load. -* **Built-ins win ties** — if a custom renderer declares `json` or `markdown`, the built-in one is used. -* **Failures are tolerated** — a plugin that cannot be loaded is reported as a warning and skipped; it does not abort generation. +- implement `IErrorDocumentationRenderer`; +- be public; +- have a parameterless constructor; +- use the contract assembly resolved by the CLI process; +- target a framework loadable by that CLI runtime. -## Using a renderer without the CLI +Do not deploy a private copy of `FirstClassErrors` beside the plugin: if the CLI and the plugin each load their own copy, `IErrorDocumentationRenderer` becomes two distinct types and the renderer is silently not recognized. A project reference may use `false` when appropriate for the packaging layout. -The CLI is optional — a renderer is just a class. If you obtain a catalog yourself (for instance via `SolutionErrorDocumentationGenerator`, in `FirstClassErrors.GenDoc`), rendering it is: +A plugin that cannot be loaded is reported and skipped. Review those warnings: an unknown format later in the command may simply mean its plugin failed to load. + +## Localize renderer-owned text + +A CSV schema may have no translated template text. A renderer producing headings should resolve only those headings from `request.Culture`: + +```csharp +// RendererResources is your own .resx-backed resource class, not a library type. +string heading = RendererResources.GetString("ErrorCatalog", request.Culture) + ?? "Error catalog"; +``` + +Keep these responsibilities separate: + +| Content | Owner | +| --- | --- | +| error title, description, rule, diagnostics, public messages | extraction and application resources | +| headings, labels, navigation text, table headers | renderer resources | +| codes, paths, anchors, schema field names | culture-invariant contract | + +See [Internationalization](Internationalization.en.md). + +## Use it without the CLI + +A renderer is an ordinary class. Programmatic callers can extract and render directly: ```csharp -// Use one culture for both levels: it localizes the extracted content and the rendered boilerplate. CultureInfo culture = CultureInfo.GetCultureInfo("en"); IEnumerable catalog = SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom( - "MyApp.sln", new SolutionGenerationOptions { Culture = culture }); + "MyApp.sln", + new SolutionGenerationOptions { Culture = culture }); RenderRequest request = new(RenderLayouts.Single, culture); -foreach (RenderedDocument document in new CsvErrorDocumentationRenderer().Render(catalog, request)) { + +foreach (RenderedDocument document in + new CsvErrorDocumentationRenderer().Render(catalog, request)) { File.WriteAllText(document.RelativePath, document.Content); } ``` +The same culture should be used for extraction and rendering unless mixed-language output is deliberate. + +## Renderer design checklist + +Before publishing a renderer, verify that: + +- `Format` is stable and does not collide with a built-in format; +- every declared layout is implemented; +- unsupported layouts throw `LayoutNotSupportedException`; +- output paths are relative and deterministic; +- output order is deterministic; +- machine schemas do not change accidentally; +- escaping is correct for the target format; +- renderer-owned text uses `request.Culture`; +- application-owned content is not translated a second time; +- the renderer performs no external I/O inside `Render(...)`; +- the plugin loads without warnings in the CLI environment. + --- ---- +--- \ No newline at end of file diff --git a/doc/WritingACustomRenderer.fr.md b/doc/WritingACustomRenderer.fr.md index d1e8397..18bcbcd 100644 --- a/doc/WritingACustomRenderer.fr.md +++ b/doc/WritingACustomRenderer.fr.md @@ -1,39 +1,102 @@ # Écrire son propre renderer -🌍 **Langues:** +🌍 **Langues :** 🇬🇧 [English](./WritingACustomRenderer.en.md) | đŸ‡«đŸ‡· Français (ce fichier) -Les formats intĂ©grĂ©s `json` et `markdown` couvrent les cas courants, mais n’importe quel format de sortie — HTML, CSV, un gabarit de documentation maison — peut ĂȘtre ajoutĂ© sous forme de **renderer personnalisĂ©**. Un renderer ne dĂ©pend que du modĂšle de documentation, pas de la façon dont le catalogue a Ă©tĂ© produit : en Ă©crire un est donc court et autonome. +Un renderer personnalisĂ© transforme le catalogue `ErrorDocumentation` dĂ©jĂ  extrait dans un format appartenant Ă  votre organisation : CSV, payload pour un portail documentaire, site statique maison ou autre reprĂ©sentation lisible par machine. -## Le contrat +Un renderer ne dĂ©couvre pas les projets et n’exĂ©cute pas les factories d’erreur. Il reçoit un catalogue dĂ©jĂ  extrait et dĂ©cide uniquement de sa prĂ©sentation. + +## Le workflow + +```mermaid +flowchart LR + A[Catalogue ErrorDocumentation extrait] + B[Renderer personnalisĂ©] + C[Un ou plusieurs RenderedDocument] + D[Fichiers Ă©crits par la CLI ou l'application] + + A --> B --> C --> D +``` + +Pour ajouter un format : + +1. rĂ©fĂ©rencez `FirstClassErrors` ; +2. implĂ©mentez `IErrorDocumentationRenderer` ; +3. dĂ©clarez un `Format` unique et les layouts pris en charge ; +4. renvoyez un ou plusieurs `RenderedDocument` ; +5. packagez le renderer dans un assembly chargeable ; +6. enregistrez cet assembly dans `fce.json` ou avec la CLI. -Un renderer implĂ©mente `IErrorDocumentationRenderer` (fourni dans le package `FirstClassErrors`, namespace `FirstClassErrors.GenDoc.Rendering`) : +## Le contrat ```csharp public interface IErrorDocumentationRenderer { - // La valeur choisie avec `fce generate --format <
>`. string Format { get; } - - // Les layouts que ce renderer sait produire, ex. "single", "split" (voir RenderLayouts). IReadOnlyCollection SupportedLayouts { get; } + IReadOnlyList Render( + IEnumerable catalog, + RenderRequest request); +} +``` + +Les types du contrat de rendu sont dans le namespace `FirstClassErrors.GenDoc.Rendering` du package `FirstClassErrors` ; le modĂšle documentaire lui-mĂȘme (`ErrorDocumentation` et les types associĂ©s) est dans le namespace `FirstClassErrors`. + +### `Format` + +`Format` est la valeur sĂ©lectionnĂ©e par `--format` : - // Transforme le catalogue en un ou plusieurs fichiers de sortie pour le layout et la culture demandĂ©s. - IReadOnlyList Render(IEnumerable catalog, RenderRequest request); +```csharp +public string Format => "csv"; +``` + +Choisissez un nom stable en minuscules. Les formats intĂ©grĂ©s restent prioritaires si un renderer personnalisĂ© rĂ©utilise `json`, `markdown` ou `html`. + +### `SupportedLayouts` + +Un layout dĂ©crit la forme de la sortie, pas son format : + +```csharp +public IReadOnlyCollection SupportedLayouts { get; } = + new[] { RenderLayouts.Single }; +``` + +Les noms intĂ©grĂ©s sont `single` et `split`, mais un renderer peut dĂ©finir une autre chaĂźne si son modĂšle de sortie l’exige. + +Rejetez explicitement un layout non supportĂ© : + +```csharp +if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) { + throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts); } ``` -`RenderedDocument` est un couple `(RelativePath, Content)`. Renvoyez un seul document pour un format mono-fichier, ou plusieurs (un index plus un fichier par erreur) pour un format multi-fichiers — le `RelativePath` sert de nom de fichier lorsque la sortie est un dossier. +### `RenderedDocument` + +Chaque document renvoyĂ© contient : + +- `RelativePath`, le chemin sous l’emplacement de sortie choisi ; +- `Content`, le contenu complet du fichier. -`RenderRequest` porte les deux choix propres Ă  chaque appel : +Renvoyez un document pour un format mono-fichier et plusieurs documents pour un site ou un layout splittĂ©. -* **`Layout`** — la valeur de `fce generate --layout <
>`. DĂ©clarez les layouts pris en charge dans `SupportedLayouts` et rejetez tout autre avec `LayoutNotSupportedException` (le renderer intĂ©grĂ© `json` ne gĂšre que `single` ; `markdown` gĂšre `single` et `split`). Un layout est une simple chaĂźne : un renderer peut donc dĂ©finir les siens. -* **`Culture`** — la langue cible. Localisez le texte fixe que vous produisez pour `request.Culture` (le *contenu* des erreurs est dĂ©jĂ  localisĂ© en amont par l’extracteur : un renderer ne localise donc que son propre gabarit). Voir [Internationalisation](Internationalisation.fr.md). +Gardez les chemins relatifs, dĂ©terministes et sĂ»rs. N’écrivez pas directement les fichiers dans `Render(...)` : l’appelant possĂšde la destination. -Le contrat et le modĂšle (`ErrorDocumentation`, `ErrorDiagnostic`, 
) sont livrĂ©s dans le package `FirstClassErrors`, qui cible **.NET Standard 2.0** — un renderer n’a donc besoin que de cette seule rĂ©fĂ©rence, que la plupart des projets possĂšdent dĂ©jĂ . +### `RenderRequest` -## Un exemple minimal +`RenderRequest` porte : + +- `Layout`, sĂ©lectionnĂ© avec `--layout` ; +- `Culture`, utilisĂ©e pour les titres, libellĂ©s et textes fixes appartenant au renderer ; +- `ServiceName`, issu de `--service-name` ou de la configuration, utilisĂ© par les renderers qui Ă©mettent des types de problĂšme RFC 9457 (« Problem Details » pour les API HTTP) de la forme `urn:problem:{service}:{code}` ; `null` si non configurĂ©. + +Le contenu du catalogue a dĂ©jĂ  Ă©tĂ© localisĂ© pendant l’extraction. Un renderer ne doit pas retraduire les titres, rĂšgles, messages ou diagnostics des erreurs. + +## Renderer CSV minimal complet ```csharp +using System; +using System.Collections.Generic; using System.Linq; using FirstClassErrors; @@ -43,36 +106,46 @@ public sealed class CsvErrorDocumentationRenderer : IErrorDocumentationRenderer public string Format => "csv"; - // Un seul fichier CSV — ce renderer ne gĂšre que le layout « single ». - public IReadOnlyCollection SupportedLayouts { get; } = new[] { RenderLayouts.Single }; + public IReadOnlyCollection SupportedLayouts { get; } = + new[] { RenderLayouts.Single }; + + public IReadOnlyList Render( + IEnumerable catalog, + RenderRequest request) { - public IReadOnlyList Render(IEnumerable catalog, RenderRequest request) { if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) { throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts); } - var rows = catalog.Select(error => $"{error.Code},{Quote(error.Title)}"); - var content = "code,title\n" + string.Join("\n", rows); + IEnumerable rows = catalog.Select(error => + $"{Quote(error.Code.ToString())},{Quote(error.Title)}"); - return new[] { new RenderedDocument("errors.csv", content) }; + string content = "code,title\n" + string.Join("\n", rows); + + return new[] { + new RenderedDocument("errors.csv", content) + }; } - private static string Quote(string? value) => $"\"{(value ?? string.Empty).Replace("\"", "\"\"")}\""; + private static string Quote(string? value) { + string escaped = (value ?? string.Empty).Replace("\"", "\"\""); + return $"\"{escaped}\""; + } } ``` -C’est un renderer complet. (Ce CSV n’a aucun texte fixe Ă  traduire ; un renderer qui Ă©met des titres ou des libellĂ©s les lirait depuis des ressources indexĂ©es par `request.Culture`.) +Ce renderer est dĂ©terministe, prend en charge un seul layout et renvoie un fichier complet. -## Le brancher sur la CLI +## L’enregistrer dans la CLI -Compilez votre renderer dans une bibliothĂšque, puis enregistrez-le : +Compilez le renderer dans une bibliothĂšque, puis enregistrez l’assembly : ```bash fce config renderer add ./plugins/MyCompany.Renderers.dll -fce generate --solution MyApp.sln --format csv --output errors.csv +fce config renderer list ``` -`fce config renderer add` inscrit le chemin de la bibliothĂšque dans `fce.json` (vous pouvez aussi Ă©diter le fichier Ă  la main). Au moment de la gĂ©nĂ©ration, la CLI charge les assemblies rĂ©fĂ©rencĂ©es, y dĂ©couvre chaque renderer public dotĂ© d’un constructeur sans paramĂštre, et sĂ©lectionne celui dont le `Format` correspond Ă  `--format`. `fce config renderer list` affiche les formats intĂ©grĂ©s et configurĂ©s, et un `--format` inconnu liste ce qui est disponible. +La configuration contient le chemin enregistrĂ© : ```json { @@ -80,38 +153,94 @@ fce generate --solution MyApp.sln --format csv --output errors.csv } ``` -Les chemins sont absolus ou relatifs Ă  `fce.json`, de sorte qu’une configuration est portable avec ses plugins. +Les chemins peuvent ĂȘtre absolus ou relatifs Ă  `fce.json`. Les chemins relatifs rendent la configuration du dĂ©pĂŽt portable. + +GĂ©nĂ©rez avec le nouveau format : + +```bash +fce generate \ + --solution ./MyApp.sln \ + --format csv \ + --layout single \ + --output ./artifacts/errors.csv +``` + +## RĂšgles de dĂ©couverte et de chargement -### À savoir +La CLI dĂ©couvre les types de renderer publics dans les assemblies configurĂ©s. Un renderer doit : -* **Constructeur sans paramĂštre** — la CLI instancie les renderers par rĂ©flexion. -* **Contrat partagĂ©** — rĂ©fĂ©rencez `FirstClassErrors`, mais ne livrez pas votre propre copie de cet assembly Ă  cĂŽtĂ© de la CLI : le type du renderer doit se rĂ©soudre vers l’assembly de contrat de la CLI. RĂ©fĂ©rencez-le sans le copier (par ex. `false` sur la rĂ©fĂ©rence), ou appuyez-vous sur la version identique dĂ©jĂ  prĂ©sente Ă  cĂŽtĂ© de la CLI. -* **Framework cible** — la CLI charge le plugin dans son propre processus ; compilez-le pour un framework qu’elle peut charger. -* **Les intĂ©grĂ©s gagnent les Ă©galitĂ©s** — si un renderer personnalisĂ© dĂ©clare `json` ou `markdown`, c’est l’intĂ©grĂ© qui est utilisĂ©. -* **Les Ă©checs sont tolĂ©rĂ©s** — un plugin impossible Ă  charger est signalĂ© par un avertissement et ignorĂ© ; il n’interrompt pas la gĂ©nĂ©ration. +- implĂ©menter `IErrorDocumentationRenderer` ; +- ĂȘtre public ; +- possĂ©der un constructeur sans paramĂštre ; +- utiliser l’assembly de contrat rĂ©solu par le processus CLI ; +- cibler un framework chargeable par ce runtime. -## Utiliser un renderer sans la CLI +Ne dĂ©ployez pas de copie privĂ©e de `FirstClassErrors` Ă  cĂŽtĂ© du plugin : si la CLI et le plugin chargent chacun leur propre copie, `IErrorDocumentationRenderer` devient deux types distincts et le renderer n’est silencieusement pas reconnu. Une rĂ©fĂ©rence projet peut utiliser `false` lorsque ce choix correspond au packaging. -La CLI est optionnelle — un renderer n’est qu’une classe. Si vous obtenez un catalogue vous-mĂȘme (par exemple via `SolutionErrorDocumentationGenerator`, dans `FirstClassErrors.GenDoc`), le rendre se rĂ©sume Ă  : +Un plugin impossible Ă  charger est signalĂ© puis ignorĂ©. Examinez ces avertissements : un format inconnu peut simplement indiquer que son plugin n’a pas Ă©tĂ© chargĂ©. + +## Localiser le texte du renderer + +Un schĂ©ma CSV peut ne contenir aucun texte Ă  traduire. Un renderer avec des titres doit rĂ©soudre uniquement ses propres libellĂ©s depuis `request.Culture` : + +```csharp +// RendererResources est votre propre classe de ressources .resx, pas un type de la bibliothĂšque. +string heading = RendererResources.GetString("ErrorCatalog", request.Culture) + ?? "Error catalog"; +``` + +Gardez ces responsabilitĂ©s sĂ©parĂ©es : + +| Contenu | Responsable | +| --- | --- | +| titre, description, rĂšgle, diagnostics, messages publics | extraction et ressources applicatives | +| titres, libellĂ©s, navigation, en-tĂȘtes | ressources du renderer | +| codes, chemins, ancres, champs de schĂ©ma | contrat indĂ©pendant de la culture | + +Voir [Internationalisation](Internationalisation.fr.md). + +## L’utiliser sans la CLI + +Un renderer est une classe ordinaire. Un appelant programmatique peut extraire puis rendre directement : ```csharp -// Une seule culture pour les deux niveaux : elle localise le contenu extrait et le texte fixe rendu. CultureInfo culture = CultureInfo.GetCultureInfo("fr"); IEnumerable catalog = SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom( - "MyApp.sln", new SolutionGenerationOptions { Culture = culture }); + "MyApp.sln", + new SolutionGenerationOptions { Culture = culture }); RenderRequest request = new(RenderLayouts.Single, culture); -foreach (RenderedDocument document in new CsvErrorDocumentationRenderer().Render(catalog, request)) { + +foreach (RenderedDocument document in + new CsvErrorDocumentationRenderer().Render(catalog, request)) { File.WriteAllText(document.RelativePath, document.Content); } ``` +Utilisez la mĂȘme culture pour l’extraction et le rendu, sauf si une sortie multilingue mĂ©langĂ©e est volontaire. + +## Checklist de conception + +Avant de publier un renderer, vĂ©rifiez que : + +- `Format` est stable et ne collisionne pas avec un format intĂ©grĂ© ; +- chaque layout dĂ©clarĂ© est implĂ©mentĂ© ; +- les layouts non supportĂ©s lĂšvent `LayoutNotSupportedException` ; +- les chemins de sortie sont relatifs et dĂ©terministes ; +- l’ordre de sortie est dĂ©terministe ; +- les schĂ©mas machine ne changent pas accidentellement ; +- l’échappement correspond au format cible ; +- les textes du renderer utilisent `request.Culture` ; +- le contenu applicatif n’est pas traduit une seconde fois ; +- `Render(...)` n’effectue pas d’I/O externe ; +- le plugin se charge sans avertissement dans l’environnement CLI. + --- ---- +--- \ No newline at end of file