Distinguish corrupt subgraph records from absence - #285
Conversation
Closes #213. Closes #234. NoRecordFound now means only what the doc comments say it means: the subgraph has no such record. A record it has but cannot serve intact is CorruptRecord, naming the offending field, and a query the subgraph itself rejected is SubgraphError, carrying its messages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 18 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment: S/M/L PR Classification Guidelines:This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed. Small (S)Characteristics:
Review Effort: Would have taken 5-10 minutes Examples:
Medium (M)Characteristics:
Review Effort: Would have taken 15-30 minutes Examples:
Large (L)Characteristics:
Review Effort: Would have taken 45+ minutes Examples:
Additional Factors to ConsiderWhen deciding between sizes, also consider:
Notes:
|
Closes #213. Closes #234.
What was wrong
process_meta_queryandprocess_deployer_queryreturnedError::NoRecordFoundfor every failure short of a transport error, so "this subgraph has no such
record" and "this subgraph has the record but cannot serve it" were the same
value to every caller. Under
select_ok, a systemically corrupt record —or a subgraph rejecting the query outright — surfaced to
search,search_deployerandStore::updateasfound no matching record, and wastreated as a cache miss.
What changed
Two new
Errorvariants, and each failure now routes to the one that describesit:
NoRecordFound— and only this — for genuine absence:data.metais null,or
expressionDeployersis empty.CorruptRecord(String)for a record that was found but is unusable: amissing required field, more or fewer than one
meta, or a hex field thatdoes not decode. The message names the field (
bytecode is missing,constructorMetaHash: <hex error>,expected exactly one meta, got 2), sowhich of the eight deployer fields is bad is visible without a repro.
SubgraphError(String)for a query the subgraph itself failed: thepreviously-unread top level graphql
errorsmember, with every messagecarried, or a response with neither
datanorerrors. An emptyerrorsarray is the wire form for "no errors" and still succeeds.
select_oksemantics are unchanged: every one of these is still a non-fatalper-subgraph
Err, so a healthier subgraph still wins the race. What changes iswhat the caller is told when no subgraph wins.
Why both issues
#234 is the same finding against
process_meta_query, including its point thatthe graphql
errorsmember is never inspected. The two functions share theresponse-shape handling and the single error enum, so splitting this into two
PRs would have meant either a near-duplicate diff or one half of a collapse
fixed and the other left in place. Both are closed here; the
errorsmember isnow inspected in both queries, not only the meta one.
Tests
The round-1/round-2 mutation-test suites pinned the old collapse byte-for-byte;
#234 anticipated that a fix updates them. Every test that asserted
NoRecordFoundfor corruption now assertsCorruptRecordand its message,which is strictly more discriminating than the
matches!it replaces — amutation that swapped one field's name or dropped the decode would have survived
the old assertion. The two tests that assert genuine absence
(
test_process_meta_query_missing_meta_is_no_record_found,test_process_deployer_query_empty_no_record) are untouched and still assertNoRecordFound.New tests cover the paths that had none: graphql
errorson both queries, aresponse carrying neither
datanorerrors, and an emptyerrorsarrayalongside good data.
cargo test -p rain-metadata --lib: 311 passed, 0 failed.cargo clippy --workspace --all-targets -- -D warningsandcargo fmt --all --check: clean.QA
test_process_deployer_query_*_is_corrupt_record,test_process_meta_query_bad_hex_is_corrupt_record,test_process_meta_query_missing_data_is_subgraph_error,test_process_meta_query_graphql_errors_is_subgraph_error,test_process_deployer_query_graphql_errors_is_subgraph_error,test_process_meta_query_paths— each fails on base (verified by revertingthe fix's three mapping sites in place to base's
.or(Err(Error::NoRecordFound)),return Err(Error::NoRecordFound)and un-inspectederrors, keeping the newtests:
20 failed; 13 passedinmeta::query::, and those 20 are exactly thetests listed here).
test_process_meta_query_empty_errors_array_is_successisdeliberately NOT in that set — it passes on base and guards the fix against
over-rejecting; M3 below is what kills it.
meta::query::suite):decode_fieldboth arms ->Error::NoRecordFound(base's mapping) -> 15failed, killed by every
*_is_corrupt_recordhex/missing test andtest_process_meta_query_paths.response_datadrops theerrorsinspection -> 2 failed, killed bytest_process_meta_query_graphql_errors_is_subgraph_errorandtest_process_deployer_query_graphql_errors_is_subgraph_error.response_datadrops.filter(|errors| !errors.is_empty())-> 1 failed,killed by
test_process_meta_query_empty_errors_array_is_success.[meta]->[meta, ..](accept more than one meta) -> 1 failed, killed bytest_process_deployer_query_two_metas_is_corrupt_record.parserfield labelled"store"-> 2 failed, killed bytest_process_deployer_query_invalid_parser_hex_is_corrupt_recordandtest_process_deployer_query_null_parser_is_corrupt_record— the messageassertions, not the variant, are what catch this.
No surviving mutants.
the implementation. The doc comment says
NoRecordFoundmodels "nothingfound", so absence is the only input that may produce it; the graphql
response spec says
errorsis carried independently ofdataand that anempty
errorsarray means no errors, which fixes whattest_process_meta_query_empty_errors_array_is_successand the twographql_errorstests must assert. Expected messages are literal stringswritten from the field names in
deployer.graphql/meta.graphql, so awrong label in the implementation cannot satisfy them.
the missing-field collapses; covered, all eight named individually plus the
five null-field cases and both meta-count cases. process_meta_query reports corrupt payloads, bad hex and GraphQL errors all as NoRecordFound, indistinguishable from absence #234 asks for four items —
(1) absent
data, (2) nulldata.metastaying absence, (3) corruptrawBytes, (4) the never-inspectederrorsmember; covered 1, 2, 3, 4, with(4) applied to the deployer query as well, which process_meta_query reports corrupt payloads, bad hex and GraphQL errors all as NoRecordFound, indistinguishable from absence #234 did not ask for but
process_deployer_query reports corrupt subgraph payloads as NoRecordFound, indistinguishable from absence #213's
datapath needs.🤖 Generated with Claude Code