Skip to content

[libgraphql-core-v1] Task 16.6e: Hygiene, from_str family, Schema::resolve_span#109

Open
jeffmo wants to merge 5 commits into
mainfrom
lgcore_v1_task16.6e
Open

[libgraphql-core-v1] Task 16.6e: Hygiene, from_str family, Schema::resolve_span#109
jeffmo wants to merge 5 commits into
mainfrom
lgcore_v1_task16.6e

Conversation

@jeffmo

@jeffmo jeffmo commented Jul 8, 2026

Copy link
Copy Markdown
Owner

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 (only load_str/build_from_str existed). Inherent method mirroring the other string entry points (FromStr deliberately avoided — it would force a trait import for a builder convenience); build_from_str now delegates through it. Error type mirrors load_str's load-phase shape, documented on the method.
  • Source labels for multi-source schemas — v1 hardcoded SchemaSourceMap::from_source(source, None), so diagnostics from multi-document schemas couldn't name their source (a silent v0 regression: v0's load_str took Option<&Path>). New from_str_with_label / load_str_with_label / build_from_str_with_label variants (impl AsRef<Path>) thread the label into SchemaSourceMap.file_path; unlabeled entry points keep their clean signatures; all share a private load_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's source_map_id (None out of range); rustdoc states the schema-originated-span-only domain per AD18's cross-artifact rule, the 0-based LineCol semantics, the byte-offset column approximation (source maps store line_starts only), and builtin→0:0.
  • Hygiene: stale "build() is currently todo!()" rustdoc in validators/mod.rs replaced with accurate module docs; the completed TODO(Task 16) in union_type_validator.rs removed; the dead #[allow(dead_code)] mutation_type_name() builder accessor deleted (the field remains; build() reads it directly — removal verified reference-free).
  • schema_def test coverage — the typed query API had zero dedicated tests; new schema_def_tests.rs covers typed lookups (incl. kind-mismatch → None), typed iterators, types_implementing(), root-op accessors, and resolve_span with hand-computed positions on a multi-line schema.
  • Zeroed 9 pre-existing cargo doc warnings (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-document source_map_id, and cross-document line resolution; hand-verified resolve_span positions; 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-v10 warnings
  • Opus sub-agent review: no blockers; independently re-derived the resolve_span test's line_starts/position math, confirmed the build_from_str delegation 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.
  • Plan doc updated with [x] boxes + Completion Notes per the Execution Protocol

🤖 Generated with Claude Code

https://claude.ai/code/session_01TipQtpvuLuHjHmRsVEgwJw


Generated by Claude Code

claude added 2 commits July 8, 2026 04:30
…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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_str plus _with_label variants across the load_str / from_str / build_from_str family, threading labels into SchemaSourceMap::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_def tests 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.

Comment thread crates/libgraphql-core-v1/src/types/field_definition.rs Outdated
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>,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +281 to +282
/// This method only accepts spans that originated from this
/// `Schema` — i.e. spans found on its types, fields,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Schema in 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: Schema must be 'static + serde for bincode embedding. Brands don't survive serialization — a deserialized Span<'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

claude added 2 commits July 20, 2026 02:53
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants