/Items ignores the by-name filters a client actually sends, so the web client's genre page lists every film #120
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
#91 fixed the six id-valued filters. The by-name ones beside them are still read off the query and dropped, and one of them has a visible symptom in the pinned web bundle.
The genre page is wrong in a browser. jellyfin-web's genre page sends
Genres=, neverGenreIds=— so #91's fix does not reach it. Measured on the live pair:A viewer clicking Science Fiction is shown the whole library. This was found by driving the browser during #91's review; no scenario covers
Genres, which is why six milestones of replays never reported it.Also confirmed unbound, same shape, same cause — a field missing from
Paramsincrates/jellyfin-http/src/routes/items.rs, which is#[serde(default)]with nodeny_unknown_fields, so the parameter is accepted and dropped in silence:nameStartsWithOrGreateris the same pair and was not measured.And one the other way round:
/Itemsfilters onseriesId, which upstream'sGetItemsdoes not accept at all. The parameter is not in the controller's signature, so upstream ignores it and we narrow on it:Binding something upstream does not is the same defect as failing to bind something it does: the two servers answer differently for a parameter a client sent.
Where to start
crates/jellyfin-http/src/routes/items.rs— theParamsstruct and the mapping intoItemQuery. Upstream's binding isJellyfin.Api/Controllers/ItemsController.csand the application isJellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs.Do the whole parameter list rather than these three. Compare
Paramsagainst upstream's controller signature field by field and settle every difference in both directions — bound-but-shouldn't-be as well as unbound. That is the only way this class of bug stops being found one instance at a time, and #91 plus this issue is already twice.This interacts with the parentless-listing gate #91 added:
ItemQuery::narrows()can only see what the route binds, so an unbound filter is also invisible to that decision.How to know it is done
The three measurements above agree, a harness scenario carries
Genresand the by-name filters so a replay holds it, and the browser's genre page lists what upstream's lists.House rules
Tests run under
cargo nextest; upstream's source is the checkout inupstream/at the pin (
v12.0-rc5,6d501ba4); generated files are regenerated ratherthan edited; comments say what the code does, not how it got there. The
reference pair is
cargo xtask reference up, a run iscargo run -p jellyfin-harness -- replay --upstream URL --ours URL --scenario FILE --out DIR,and
crates/jellyfin-harness/README.mdsays what a diff means. A server startedoff current
mainbinds UDP 7359 for discovery — set[discovery] enabled = falsein a scratch config if another one is already running.
Done, on
main.The genre page was the point: the pinned web client sends
Genres=, neverGenreIds=, so #91's fix never reached it and a viewer clicking Science Fiction was shown the whole library.Rather than binding the three named here, the whole parameter list was held against upstream's controller signature in both directions.
Genres,Studios,Artists,Tags,OfficialRatings,NameLessThanandNameStartsWithOrGreaterare bound;seriesIdis gone, becauseGetItemsnever took it and we were narrowing on something upstream ignores.NARROWING_UNREADshrank by seven and stays the honest register of the rest — the four names outside it are justified againstInternalItemsQuery.HasFilters, which testsMinParentalRatingand neverMaxParentalRating.official_ratingsreproducesWhereItemOrDescendantMatches— direct,AncestorIds, thenLinkedChildren— and the name range matchesApplyNameFilters, which compares onSortName.ToLower().The batch this closed in
Seven issues went out together because they looked like one defect class. They turned out to be three roots:
/Items'Paramshad been grown one client at a time instead of held against upstream's controller signature, and/UserItems/Resumeshared it.sortBy=Namemaps toCleanNameupstream, notSortName, and a by-name search orders by relevance with no sort-name tiebreak, so ties fall out in id order.PremiereDatecoalesces withProductionYearwhere ours read the bare column.A reviewer re-derived the parameter audit mechanically — extracting every
[FromQuery]name fromGetItemsand doing set arithmetic against what we bind — and got nothing bound that upstream does not take and nothing in the register that is not an upstream parameter. This class is closed rather than narrowed, which matters because it had been found three times.Verified on a restored pair:
browsing34 → 0,items33 requests at 0,clients56 → 3,portable115 → 6.JELLYFIN_RS_REQUIRE_FIXTURES=1 cargo nextest run --workspace: 2228 passed, 0 skipped. Clippy clean. No ignore-list entry was added or touched.Two things the review caught that the batch had not
Five of the ten new scenario entries could not fail. The
defaultsnapshot has no genres, studios or tags, so fiveitems.jsonentries compared two empty pages — reverting the whole fix would still have replayed clean. They moved intoediting.json, after the save that gives one film a genre, a tag, a certificate and a studio and then takes them away, so each selects one row out of nineteen. That is the fifth time this session the same failure has turned up in a different layer, after M5's vacuous scenarios, M8'sbundle.json, #111's all-refusalsvisibility.jsonand #137's silently-skipping tests.Reading the reference can write to it.
Studios=,Artists=andAlbums=resolve through upstream'sGetStudio/GetArtist, which callCreateItemByName— so a read files aBaseItemsrow. That is how a strayBrandywine Productionsgot into the shared fixture mid-session and madeportable.jsondisagree by 149.Genres,TagsandOfficialRatingsnarrow throughItemValuesand are safe. Both halves are now written intocrates/jellyfin-harness/README.md.