Skip to content

Commit e014af5

Browse files
authored
Merge pull request #349 from quarto-dev/feature/bd-egcyeym9-error-reporting-split
Make quarto-error-reporting catalog-agnostic; extract quarto-error-catalog (bd-egcyeym9, Phase 2)
2 parents 337d2fd + f8cf0ed commit e014af5

46 files changed

Lines changed: 676 additions & 325 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ path = "./crates/quarto-yaml-validation"
115115
[workspace.dependencies.quarto-error-reporting]
116116
path = "./crates/quarto-error-reporting"
117117

118+
[workspace.dependencies.quarto-error-catalog]
119+
path = "./crates/quarto-error-catalog"
120+
118121
[workspace.dependencies.quarto-source-map]
119122
version = "0.1.0"
120123

claude-notes/plans/2026-06-26-extract-error-reporting-foundation.md

Lines changed: 100 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,10 @@ the **WASM cutover** — on the easiest crate, before the harder error-reporting
181181
+serde), added `README.md` + `.gitignore`. Builds on **stable** rustc 1.95
182182
(no nightly needed).
183183
- [x] **1b.** Local validation green: `cargo build`, `cargo test` (104 unit + 4
184-
doctests pass), `cargo publish --dry-run` clean. *(Gap: no GitHub Actions CI
185-
workflow committed to the new repo yet — tests were run locally. Add one.)*
184+
doctests pass), `cargo publish --dry-run` clean. **CI workflow added**
185+
(`.github/workflows/ci.yml`, commit `ee3780d`): stable Rust, test matrix
186+
Linux/macOS/**Windows** + a fmt/clippy(`-D warnings`) job. First run green on
187+
all 4 jobs — Windows build confirmed (first time this crate has built there).
186188
- [x] **1c.** Published `quarto-source-map 0.1.0` to crates.io (Carlos's personal
187189
account; `cargo owner --add github:posit-dev:<team>` deferred — weekend).
188190
- [x] **1d.** q2 cutover (branch `braid/bd-egcyeym9-source-map-extraction`):
@@ -214,33 +216,106 @@ engineering and is valuable even if the repo move slipped. Does **not** depend o
214216
Phase 1. There is **no façade**`quarto-error-reporting` keeps its name and its
215217
public surface (minus the moved catalog *data*).
216218

217-
- [ ] **2a (test first).** Pin the behaviour the carve-out must preserve, against
218-
the *current* code:
219-
- `get_docs_url("Q-0-1")` returns the quarto.org URL (with the q2 catalog
220-
active);
221-
- a new test asserting that with **no** catalog installed, `get_docs_url`
222-
returns `None` (passes only after the registry exists — write it now,
223-
expect red).
224-
- [ ] **2b.** Introduce `CatalogProvider` + the `OnceLock` registry + delegating
225-
free-functions in `quarto-error-reporting`; keep `ErrorCodeInfo` here.
226-
Repoint `diagnostic.rs:290` at `catalog()`. Green.
227-
- [ ] **2c.** Create `quarto-error-catalog` crate: move `error_catalog.json` + the
228-
`ERROR_CATALOG` static + the `QuartoCatalog` provider + `install()` there.
229-
Move the catalog-data tests with it. Wire `install()` into q2 binary/WASM/test
230-
entry points.
231-
- [ ] **2d.** Put `json.rs` behind a default-off `json` feature in
232-
`quarto-error-reporting`; have q2's dependents that use it enable
233-
`features = ["json"]`. Confirm the 9 dependents + the 2 `quarto-core` catalog
234-
callers + all `json`/`coalesce` consumers compile **unchanged** (only feature
235-
flags and the `ERROR_CATALOG` import path may move).
236-
- [ ] **2e.** Audit manifests: `schemars` becomes `json`-feature-only; drop
237-
`once_cell` if the registry's `OnceLock` makes it unused; `url` stays.
238-
- [ ] **2f.** `cargo xtask verify` green (touches `quarto-core` → hub-client/WASM;
239-
do NOT `--skip-hub-build`).
219+
- [x] **2a (test first).** Behaviour pinned: `empty_catalog_returns_none`
220+
(direct, global-free) + `installed_catalog_is_used_by_lookups` (the single
221+
global-mutating test) in `quarto-error-reporting`; the positive
222+
`get_docs_url("Q-0-1") → quarto.org` case relocated to `quarto-error-catalog`
223+
integration tests (`install_makes_get_docs_url_resolve`,
224+
`diagnostic_docs_url_resolves_after_install`).
225+
- [x] **2b.** `quarto-error-reporting` now catalog-agnostic: `CatalogProvider`
226+
trait + `EmptyCatalog` + `OnceLock` registry + `install_catalog` in
227+
`catalog.rs`; `get_docs_url`/`get_error_info`/`get_subsystem` keep their
228+
signatures but delegate to the installed provider (the `&'static` lifetime
229+
survives via `catalog(): &'static dyn CatalogProvider`). `ERROR_CATALOG`
230+
static + `include_str!` removed; `lib.rs` re-exports updated. `diagnostic.rs`
231+
`docs_url()` unchanged (still calls the local delegating fn); its positive
232+
doctest/test relaxed/relocated.
233+
- [x] **2c.** New `quarto-error-catalog` crate: `error_catalog.json` (git-moved) +
234+
`ERROR_CATALOG` (Lazy) + `QuartoCatalog: CatalogProvider` + `install()`; the
235+
example moved here; the 10 data-presence tests ported (direct map access).
236+
`install()` wired into the `q2` binary `main`. **WASM deliberately does
237+
NOT install** (see the 2f note): the WASM bridge never surfaces docs URLs
238+
(`JsonDiagnostic` has no `docs_url` field), and installing would
239+
`include_str!` the 46 KB catalog into the bundle, pushing the WASM past
240+
hub-client's 35 MiB PWA precache limit. A legitimate "embedder installs
241+
nothing → EmptyCatalog" choice. The 2 `quarto-core` data-presence `#[test]`s
242+
now query
243+
`quarto_error_catalog::ERROR_CATALOG` directly (dev-dep added). **Audit
244+
script + ~25 path references updated** to `crates/quarto-error-catalog/…`;
245+
`scripts/audit-error-codes.py` passes (exit 0). Full workspace nextest:
246+
**10240 passed**.
247+
- [x] **2d.** `json.rs` now behind a **default-off `json` feature** (`lib.rs`
248+
`#[cfg(feature = "json")]` on the module + re-export; `tests/schema_drift.rs`
249+
gated with `#![cfg(feature = "json")]`). Only **4** crates use the wire
250+
symbols (`quarto`, `quarto-core`, `quarto-preview`, `wasm-quarto-hub-client`);
251+
each now declares `features = ["json"]`. `to_json` (uses `serde_json::json!`,
252+
not the module) and `coalesce.rs` stay unconditional. Verified by `cargo
253+
tree`: `schemars` **absent** by default, present with `--features json`.
254+
- [x] **2e.** `schemars` made `optional = true` + `[features] json =
255+
["dep:schemars"]`. `once_cell` **dropped** (registry uses `std::sync::OnceLock`;
256+
confirmed unused via `cargo tree`). `url` stays. Two clippy fixes in the new
257+
code (`map().unwrap_or``match`; needless doctest `fn main`).
258+
- [x] **2f.** `cargo xtask verify` **GREEN — all 14 steps** (incl. WASM build +
259+
hub-client tests). Two failures found + fixed en route: (1) two clippy lints
260+
in the new code (Step 1); (2) the WASM build (Step 7) broke hub-client's vite
261+
PWA step — wiring `install()` into the WASM bootstrap `include_str!`'d the
262+
46 KB catalog and forced it past the 35 MiB precache limit (`vite.config.ts`
263+
`maximumFileSizeToCacheInBytes`). Fixed *soundly* (not by raising the limit):
264+
removed the WASM `install()` + `quarto-error-catalog` dep, since the WASM
265+
surfaces no docs URLs — pure dead weight there. WASM now 36,684,365 B
266+
(15.8 KB **under** the limit). Workspace nextest **10240 passed**.
267+
268+
**Phase 2 is COMPLETE.** `quarto-error-reporting` is now catalog-agnostic (a
269+
`CatalogProvider` seam + `EmptyCatalog` default, no embedded `Q-*` data); the q2
270+
policy lives in the new `quarto-error-catalog`; `json` is a default-off feature.
271+
The crate is ready to carve out (Phase 3) once the Phase-1-style repo/publish
272+
machinery is pointed at it. Uncommitted on `braid/bd-egcyeym9-error-reporting-split`.
240273

241274
> At the end of Phase 2, q2 still builds `quarto-error-reporting` from a path dep;
242275
> it is now catalog-agnostic and cleanly carve-able.
243276
277+
### Phase 2 — implementation notes (investigation 2026-06-27)
278+
279+
**Blast-radius finding (much smaller than feared).** A full workspace audit shows
280+
the catalog is **completely decoupled from the production render path**:
281+
- `DiagnosticMessage` does *not* consult the catalog to build its title/message
282+
(verified: no `get_error_info`/`message_template` use in `builder.rs`/`diagnostic.rs`).
283+
- `to_text` (ariadne) and the JSON wire shape **never call `docs_url()`**; `json.rs`
284+
has no `docs_url` field. **Zero `.snap` files contain a `quarto.org/docs/errors`
285+
URL.** So the carve-out cannot change any rendered output or snapshot.
286+
- `docs_url()` has **zero** consumers anywhere in the workspace. `ErrorCodeInfo` has
287+
no external users.
288+
- The *only* catalog uses outside `quarto-error-reporting`: two `quarto-core`
289+
`#[test]`s (`project_resources.rs:1447`, `theme_diagnostic.rs:271`) asserting
290+
their codes are registered, plus the `examples/with_error_code.rs` example.
291+
292+
**Consequence:** `install()` is **behaviour-neutral today** (nothing reads the
293+
catalog in production); it matters only for the data-verification tests and for
294+
future features that surface docs URLs. This removes the test-fragility risk that
295+
would otherwise come from an uninitialised global.
296+
297+
**Finalised design.**
298+
- *`quarto-error-reporting` (catalog-agnostic):* keep `ErrorCodeInfo`; add
299+
`CatalogProvider` trait + `OnceLock<Box<dyn CatalogProvider>>` registry +
300+
`install_catalog()` + `EmptyCatalog` default. `get_docs_url`/`get_error_info`/
301+
`get_subsystem` keep their **exact signatures** (the `&'static` lifetime survives
302+
because `catalog()` returns `&'static dyn CatalogProvider`) but delegate to the
303+
installed provider. `diagnostic.rs:290` is unchanged (still calls the local
304+
`get_docs_url`). Remove `error_catalog.json` + the `ERROR_CATALOG` static + the
305+
data tests + the example. Drop the `ERROR_CATALOG` re-export from `lib.rs`.
306+
- *`quarto-error-catalog` (new q2 crate):* `error_catalog.json` + the loader +
307+
`QuartoCatalog: CatalogProvider` + `install()`. Houses the moved data-presence
308+
tests, the moved doctests (e.g. `get_subsystem("Q-0-1") == Some("internal")`),
309+
and the moved example. Deps: `quarto-error-reporting`, `serde`, `serde_json`,
310+
`once_cell`.
311+
- *Test seams:* test providers/`EmptyCatalog` are exercised **directly** (no
312+
global) wherever possible; exactly one test asserts the global-empty default and
313+
one asserts global-install delegation — safe under nextest's process-per-test
314+
(and per-process doctests). The two `quarto-core` tests gain a
315+
`quarto-error-catalog` dev-dependency and call `quarto_error_catalog::install()`.
316+
- *Binaries/WASM:* wire `quarto_error_catalog::install()` into the `q2` binary
317+
`main` and the WASM bootstrap (future-proofing; behaviour-neutral now).
318+
244319
## Phase 3 — Extract `quarto-error-reporting` into a *separate* repo and cut q2 over
245320

246321
Requires Phase 1 (source-map published) **and** Phase 2 (carve-out done).

crates/qmd-syntax-helper/src/conversions/q_2_11.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This conversion rule fixes Q-2-11 errors by adding closing double quotes
44
// where they are missing at the end of blocks.
55
//
6-
// Error catalog entry: crates/quarto-error-reporting/error_catalog.json
6+
// Error catalog entry: crates/quarto-error-catalog/error_catalog.json
77
// Error code: Q-2-11
88
// Title: "Unclosed Double Quote"
99
// Message: "I reached the end of the block before finding a closing '\"' for the quote."

crates/qmd-syntax-helper/src/conversions/q_2_12.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This conversion rule fixes Q-2-12 errors by adding closing star marks
44
// where they are missing at the end of blocks.
55
//
6-
// Error catalog entry: crates/quarto-error-reporting/error_catalog.json
6+
// Error catalog entry: crates/quarto-error-catalog/error_catalog.json
77
// Error code: Q-2-12
88
// Title: "Unclosed Star Emphasis"
99
// Message: "I reached the end of the block before finding a closing '*' for the emphasis."

crates/qmd-syntax-helper/src/conversions/q_2_13.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This conversion rule fixes Q-2-13 errors by adding closing '**' marks
44
// where they are missing at the end of blocks.
55
//
6-
// Error catalog entry: crates/quarto-error-reporting/error_catalog.json
6+
// Error catalog entry: crates/quarto-error-catalog/error_catalog.json
77
// Error code: Q-2-13
88
// Title: "Unclosed Strong Star Emphasis"
99
// Message: "I reached the end of the block before finding a closing '**' for the strong emphasis."

crates/qmd-syntax-helper/src/conversions/q_2_15.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Q-2-15: Unclosed Strong Underscore Emphasis
22
//
3-
// Error catalog entry: crates/quarto-error-reporting/error_catalog.json
3+
// Error catalog entry: crates/quarto-error-catalog/error_catalog.json
44
// Error code: Q-2-15
55
// Title: "Unclosed Strong Underscore Emphasis"
66
// Message: "I reached the end of the block before finding a closing '__' for the strong emphasis."

crates/qmd-syntax-helper/src/conversions/q_2_16.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This conversion rule fixes Q-2-16 errors by adding closing superscript marks
44
// where they are missing at the end of blocks.
55
//
6-
// Error catalog entry: crates/quarto-error-reporting/error_catalog.json
6+
// Error catalog entry: crates/quarto-error-catalog/error_catalog.json
77
// Error code: Q-2-16
88
// Title: "Unclosed Superscript"
99
// Message: "I reached the end of the block before finding a closing '^' for the superscript."

crates/qmd-syntax-helper/src/conversions/q_2_17.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This conversion rule fixes Q-2-17 errors by adding closing subscript marks
44
// where they are missing at the end of blocks.
55
//
6-
// Error catalog entry: crates/quarto-error-reporting/error_catalog.json
6+
// Error catalog entry: crates/quarto-error-catalog/error_catalog.json
77
// Error code: Q-2-17
88
// Title: "Unclosed Subscript"
99
// Message: "I reached the end of the block before finding a closing '~' for the subscript."

crates/qmd-syntax-helper/src/conversions/q_2_18.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This conversion rule fixes Q-2-18 errors by adding closing strikeout marks
44
// where they are missing at the end of blocks.
55
//
6-
// Error catalog entry: crates/quarto-error-reporting/error_catalog.json
6+
// Error catalog entry: crates/quarto-error-catalog/error_catalog.json
77
// Error code: Q-2-18
88
// Title: "Unclosed Strikeout"
99
// Message: "I reached the end of the block before finding a closing '~~' for the strikeout."

0 commit comments

Comments
 (0)