[libgraphql-core-v1] Task 16.6e: Hygiene, from_str family, Schema::resolve_span#109
[libgraphql-core-v1] Task 16.6e: Hygiene, from_str family, Schema::resolve_span#109jeffmo wants to merge 5 commits into
Conversation
…span Implements Task 16.6e. Adds the small public APIs the Task 16.5 audit found claimed-but-missing, and clears the stale-doc debt in the validators: - SchemaBuilder::from_str constructs a builder from schema source (the API Disposition ledger named it; only load_str/build_from_str existed). build_from_str now delegates through it. - Source labels for multi-source schemas: new from_str_with_label / load_str_with_label / build_from_str_with_label variants thread an optional label (impl AsRef<Path>) into SchemaSourceMap.file_path, so diagnostics from multi-document schemas can name their source. Unlabeled entry points keep their clean signatures; all variants share a private load_str_impl. Task 19's operation builders will copy this shape. - Schema::resolve_span(Span) -> Option<LineCol> per AD18: indexes the schema's source maps by the span's source_map_id (None when out of range), rustdoc states the schema-originated-span domain per AD18's cross-artifact rule. - Stale rustdoc fixed: validators/mod.rs claimed build() was still todo!(); union_type_validator carried an already-completed TODO. - Dead #[allow(dead_code)] mutation_type_name() accessor removed from SchemaBuilder (build() reads the field directly). - New schema_def_tests.rs covers the previously-untested typed query API: typed lookups, typed iterators, types_implementing, root-op accessors, and resolve_span (hand-computed positions on a multi-line schema; out-of-range -> None; builtin -> 0:0). - Zeroed 9 pre-existing cargo-doc warnings so the doc gate holds. 17 tests + 2 running doctests added. All workspace tests, clippy -Dwarnings, and cargo doc pass clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TipQtpvuLuHjHmRsVEgwJw
…type rationale Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TipQtpvuLuHjHmRsVEgwJw
There was a problem hiding this comment.
Pull request overview
Implements Task 16.6e for libgraphql-core-v1 by hardening schema-building ergonomics and diagnostics: adds the missing SchemaBuilder::from_str API family (including label-threading variants), implements Schema::resolve_span, and performs hygiene/doc fixes with expanded schema_def test coverage.
Changes:
- Added
SchemaBuilder::from_strplus_with_labelvariants across theload_str/from_str/build_from_strfamily, threading labels intoSchemaSourceMap::file_path. - Implemented
Schema::resolve_span(Span) -> Option<LineCol>using the schema’s stored source maps. - Cleaned up stale docs/dead code and added new
schema_deftests covering typed lookups/iterators, root-op accessors, and span resolution.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| libgraphql-core-v1-plan.md | Marks Task 16.6e complete and documents the API/design outcomes. |
| crates/libgraphql-core-v1/src/validators/union_type_validator.rs | Updates validator docs to reflect current build wiring. |
| crates/libgraphql-core-v1/src/validators/mod.rs | Converts stale item docs to accurate module-level docs with correct link. |
| crates/libgraphql-core-v1/src/types/field_definition.rs | Adjusts rustdoc to avoid broken intra-doc link (but introduces internal-task wording). |
| crates/libgraphql-core-v1/src/span.rs | Fixes rustdoc link for ByteSpan. |
| crates/libgraphql-core-v1/src/schema/tests/schema_def_tests.rs | Adds dedicated typed schema API + resolve_span tests. |
| crates/libgraphql-core-v1/src/schema/tests/schema_builder_tests.rs | Adds tests for from_str and label threading across multi-source schemas. |
| crates/libgraphql-core-v1/src/schema/tests/mod.rs | Wires in the new schema_def_tests module. |
| crates/libgraphql-core-v1/src/schema/schema_def.rs | Adds Schema::resolve_span public API implementation + rustdoc. |
| crates/libgraphql-core-v1/src/schema/schema_builder.rs | Adds from_str and label variants; refactors shared load_str_impl; threads labels into SchemaSourceMap. |
| crates/libgraphql-core-v1/src/schema_source_map.rs | Clarifies docs and links Schema explicitly. |
| crates/libgraphql-core-v1/src/error_note.rs | Fixes intra-doc links and span type reference. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Per PR review: public rustdoc should not reference internal roadmap
items ("planned in Task 18") -- reworded to point at the operation-layer
FieldSelection concept without embedding plan-internal task IDs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TipQtpvuLuHjHmRsVEgwJw
| pub fn load_str_with_label( | ||
| &mut self, | ||
| source: &str, | ||
| label: impl AsRef<Path>, |
There was a problem hiding this comment.
Why did we invent the term "label" (here and in other APIs) rather than just referring to this by what it actually is/represents? (e.g. source_path or path)? Would it be better to use source_path/path/something semantically closer to what it is?
There was a problem hiding this comment.
You're right, and it's fixed in 2a1888c: *_with_label → *_with_source_path, with the parameter, private impl, docs, tests, and plan notes renamed to match. "Label" was trying to signal one nuance — the path needn't exist on disk (it's a diagnostic identifier: "<inline schema>", a URL, a test name all work) — but the parameter is typed impl AsRef<Path> and lands in a field literally named file_path, so the name was fighting both the type and the storage. That nuance now lives in the rustdoc instead of the name.
Generated by Claude Code
| /// This method only accepts spans that originated from this | ||
| /// `Schema` — i.e. spans found on its types, fields, |
There was a problem hiding this comment.
I think the answer is probably no, but I'm curious if there are any clever tricks we might employ to help ensure -- at the type level -- that a given Span is correctly associated with a given Schema? Not the end of the world if not (or if such a trick would impose overhead on the API and/or error message story or would otherwise have unworthy trade-offs), but I'd like you to think about it and tell me what you can come up with (if anything).
There was a problem hiding this comment.
Thought about this a fair bit. There ARE tricks; I think none of the instance-level ones are worth their cost here, but there's a domain-level middle ground worth considering for Task 18. The menu:
1. Lifetime branding / generativity (the only true type-level instance guarantee). GhostCell-style: Schema construction introduces a fresh invariant lifetime 'brand (via a closure: SchemaBuilder::build_with(|schema: Schema<'brand>| ...)), spans it hands out become Span<'brand>, and resolve_span(Span<'brand>) only accepts same-brand spans. Two different Schemas can never exchange spans — the compiler proves it. Why I'd reject it here:
- It's viral. Every span-carrying type grows the brand:
GraphQLType<'brand>,FieldDefinition<'brand>, every error type… the entire crate surface. - Brands are closure-scoped. You can't store a branded
Schemain a plain struct or return it up the stack without the brand infecting the container. That kills the "build a schema, keep it around, serve requests" usage pattern that is this crate's whole point. - Fatal conflict with AD6/macro embedding:
Schemamust be'static+ serde for bincode embedding. Brands don't survive serialization — a deserializedSpan<'brand>is meaningless, so the macro path can't exist under branding.
2. Per-schema runtime identity tag. Stamp each Schema with a unique id at build(), widen Span to carry it, resolve_span returns None on mismatch. Honest assessment: it only upgrades "wrong position" to "None", costs 4–8 bytes on a Copy type that's stored everywhere, and muddies serde/PartialEq determinism (bincode round-trip equality would need the id excluded or regenerated). Not worth it.
3. Domain-level newtypes (the one I'd actually consider). The realistic misuse class per AD18 isn't schema-A-vs-schema-B (one schema per process is overwhelmingly the norm) — it's schema-span vs operation-span, once Task 18 introduces operation documents whose spans use the same dense id space. That confusion CAN be caught at compile time cheaply: keep Span as the raw representation, but have operation-side types store/expose a DocSpan(Span) newtype (or a ZST-marker generic) so Schema::resolve_span simply won't accept an operation span and vice versa. Zero runtime cost, no serde impact (schema-side Span unchanged), and the churn lands entirely in operation types that don't exist yet — free if we decide before 18, expensive after.
Recommendation: keep resolve_span as-is for instance-level (the Option + documented span-domain contract), and if you like #3 I'll amend Task 18's spec to make operation spans a distinct type at API boundaries. Happy to do that as part of this PR's plan-doc or as a one-liner in the next one — say the word.
Generated by Claude Code
…points Per PR review: "label" was an invented term for something that is typed as impl AsRef<Path> and stored in SchemaSourceMap.file_path -- name the thing what it is. load_str_with_label/from_str_with_label/ build_from_str_with_label become *_with_source_path, with parameter, docs, tests, and plan notes renamed to match. The one nuance "label" was trying to carry (the path needn't exist on disk -- it's a diagnostic identifier) now lives in the rustdoc instead of the name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TipQtpvuLuHjHmRsVEgwJw
CI's clippy moved to 1.97 since this branch last ran, introducing two lints that fire on code untouched by this PR (any new PR would fail): - needless_return_with_question_mark: eight `return Err(...)?;` sites in the old libgraphql-core (schema_builder + three type builders) -- the exact `return Err(e)?` anti-pattern the Task 16.5 audit had flagged as a nit. The `?` on a `Result::Err` is a no-op; removed. - question_mark: libgraphql-parser's get_line() used a match with `None => return None` where `?` suffices; rewritten per clippy's suggestion with the bail-out comment preserved. No behavior changes; v0-core (167) and parser (764) test suites green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TipQtpvuLuHjHmRsVEgwJw
Summary
Task 16.6e (fifth of six schema-hardening PRs): the hygiene sweep + the two small public APIs the Task 16.5 audit found claimed-but-missing.
What & why
SchemaBuilder::from_str— named by the API-Disposition ledger but never implemented (onlyload_str/build_from_strexisted). Inherent method mirroring the other string entry points (FromStrdeliberately avoided — it would force a trait import for a builder convenience);build_from_strnow delegates through it. Error type mirrorsload_str's load-phase shape, documented on the method.SchemaSourceMap::from_source(source, None), so diagnostics from multi-document schemas couldn't name their source (a silent v0 regression: v0'sload_strtookOption<&Path>). Newfrom_str_with_label/load_str_with_label/build_from_str_with_labelvariants (impl AsRef<Path>) thread the label intoSchemaSourceMap.file_path; unlabeled entry points keep their clean signatures; all share a privateload_str_impl. Task 19's operation builders copy this shape.Schema::resolve_span(Span) -> Option<LineCol>— AD18 lists it as public API but nothing implemented it. Indexes the schema's source maps by the span'ssource_map_id(Noneout of range); rustdoc states the schema-originated-span-only domain per AD18's cross-artifact rule, the 0-basedLineColsemantics, the byte-offset column approximation (source maps storeline_startsonly), and builtin→0:0.todo!()" rustdoc invalidators/mod.rsreplaced with accurate module docs; the completed TODO(Task 16) inunion_type_validator.rsremoved; the dead#[allow(dead_code)]mutation_type_name()builder accessor deleted (the field remains;build()reads it directly — removal verified reference-free).schema_deftest coverage — the typed query API had zero dedicated tests; newschema_def_tests.rscovers typed lookups (incl. kind-mismatch →None), typed iterators,types_implementing(), root-op accessors, andresolve_spanwith hand-computed positions on a multi-line schema.cargo docwarnings (broken/wrong-crate intra-doc links, private-item links) — semantic fixes, not silencing.Tests
17 new tests + 2 running doctests (v1 crate 295 → 310 + 10 doctests). Highlights: two-source label-threading test asserting per-source
file_path, per-documentsource_map_id, and cross-document line resolution; hand-verifiedresolve_spanpositions; out-of-range →None; builtin → 0:0.Verification
cargo test --workspace— 1417 green;cargo clippy --workspace --tests -- -Dwarnings— clean;cargo doc --no-deps -p libgraphql-core-v1— 0 warningsresolve_spantest'sline_starts/position math, confirmed thebuild_from_strdelegation is behaviorally identical on error paths, verified all three spot-checked doc-link fixes are semantic, and confirmed the label-threading test exercises real cross-document resolution. Its one worthwhile nit (error-type rationale in rustdoc) applied as the follow-up commit.[x]boxes + Completion Notes per the Execution Protocol🤖 Generated with Claude Code
https://claude.ai/code/session_01TipQtpvuLuHjHmRsVEgwJw
Generated by Claude Code