diff --git a/openfut-adapter-fifa17/src/fut/owned_query.rs b/openfut-adapter-fifa17/src/fut/owned_query.rs index 3cc33a1..5f7699e 100644 --- a/openfut-adapter-fifa17/src/fut/owned_query.rs +++ b/openfut-adapter-fifa17/src/fut/owned_query.rs @@ -69,6 +69,18 @@ pub struct Fifa17ClubQuery { pub start: Option, /// Pagination page size. pub count: Option, + /// `defId=` — a comma-joined list of DEFINITION ids. The documented club + /// grammar says the client sends EITHER the filter block above OR this list, + /// never both. + /// + /// CAPTURED BUT NOT APPLIED, deliberately. The grammar is single-source (one + /// decompile plus one live log line, and that log line carried no `defId`), + /// so the exact semantics — definition id as `resourceId`, presumably — are + /// not confirmed against an observed request. Filtering on a wrong reading + /// would turn "too many items" into "zero items", which is the worse failure. + /// The host logs it instead, so the first real occurrence is visible and the + /// filter can be written against evidence rather than a guess. + pub def_ids: Vec, } /// Minimal percent/`+` decoding, dependency-free. FIFA sends bare tokens and @@ -128,6 +140,14 @@ pub fn parse_club_query(query: &str) -> Fifa17ClubQuery { "sort" => out.sort = Some(v), "start" => out.start = v.parse().ok(), "count" => out.count = v.parse().ok(), + // Comma-joined, digits only — the same reading the item-definition + // routes already use for this parameter. + "defId" => { + out.def_ids = v + .split(',') + .filter_map(|d| d.trim().parse::().ok()) + .collect() + } _ => {} } } @@ -339,6 +359,7 @@ mod tests { sort: Some("desc".into()), start: Some(10), count: Some(11), + def_ids: Vec::new(), } ); } @@ -528,3 +549,25 @@ mod tests { ); } } + +#[cfg(test)] +mod def_id_tests { + use super::*; + + /// `defId=` is CAPTURED so the host can report it, and deliberately does NOT + /// participate in the Core query — narrowing on an unconfirmed reading of the + /// parameter would answer "zero items" where today we answer "too many". + #[test] + fn def_id_list_is_captured_but_never_narrows_the_core_query() { + let q = parse_club_query("?year=2017&type=player&defId=20801,117617092,84044103"); + assert_eq!(q.def_ids, vec![20801, 117_617_092, 84_044_103]); + assert_eq!(q.item_type.as_deref(), Some("player")); + + // Non-numeric entries are dropped rather than poisoning the list. + let q = parse_club_query("?defId=20801,,notanid,42"); + assert_eq!(q.def_ids, vec![20801, 42]); + + // Absent means empty, never a phantom filter. + assert!(parse_club_query("?type=player").def_ids.is_empty()); + } +} diff --git a/openfut-utas-host/src/lib.rs b/openfut-utas-host/src/lib.rs index acf9e10..6ad85d9 100644 --- a/openfut-utas-host/src/lib.rs +++ b/openfut-utas-host/src/lib.rs @@ -2206,6 +2206,22 @@ pub fn handle_club(query: &str, deps: &ClubDeps<'_>) -> (WireResponse, ClubLog) if !deps.hidden.is_empty() { filter.push_str(&format!(",hidden={}", deps.hidden.len())); } + // The documented club grammar allows a comma-joined `defId=` list INSTEAD of + // the filter block, and this host does not narrow on it. That grammar is + // single-source and no observed request has ever carried one, so guessing the + // semantics could turn "too many items" into "zero items". Make the first + // real occurrence impossible to miss instead of silently answering wrong. + if !raw.def_ids.is_empty() { + filter.push_str(&format!(",defId={}", raw.def_ids.len())); + eprintln!( + "utas-host owner=RUST route=club NOTICE unhandled defId list ({} id(s): {:?}) \ + — the response is NOT narrowed to them. This is the first observation of a \ + parameter only ever seen in a decompile; capture the full request and \ + implement the filter against it.", + raw.def_ids.len(), + &raw.def_ids[..raw.def_ids.len().min(8)] + ); + } match deps.core.query_owned(&base.to_query_pairs()) { Ok(page) => {