Phase 12: bridge completeness — expanded mapper, request replay, full shaper coverage

Mapper (src/mapper.rs):
- Restructured into ExactRoute + PrefixRoute tables (was a flat match list)
- 38 exact mappings (up from 18): SBC, Draft, FUT Champs, Division Rivals,
  Card Upgrades, Notifications, Settings, Trade pile, Club update, Stats, etc.
- 7 prefix-matched routes for path-param endpoints:
  /ut/game/fut/draft/{id}[/pick|/abandon]  → /draft/sessions/:id[/pick|/abandon]
  /ut/game/fut/champs/{id}/result|claim    → /fut-champs/:id/result|claim
  /ut/game/fut/store/pack/{id}/open        → /packs/open/:id
  /ut/game/fut/item/{id} DELETE            → /collection/:id (quick-sell)
  /ut/game/fut/trade/{id} DELETE           → /market/listings/:id (cancel)
  /ut/game/fut/sbc/set/{id}/submission     → /sbc/submit
- map_to_core_with_method() public helper preserves HTTP method for prefix routes
- 19 mapper unit tests (up from 6)

Replay (src/routes/admin.rs):
- POST /_bridge/captures/:id/replay — loads a capture from disk, re-runs it
  against Core with original headers+body, returns original vs new response
  plus a structured JSON diff for iterative shaper/handler development

Shaper (src/shaper.rs):
- 14 new shaper functions covering every mapped Core path:
  collection, cards, objectives, notifications, division, statistics,
  sbc, pack-open, draft, fut-champs, chemistry-styles, my-listings
- Prefix dispatch for /packs/open/, /draft/sessions/, /fut-champs/ paths
- All 25 unit tests pass, clippy clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 17:11:06 -07:00
parent 00839f5f1b
commit b7da8b6e83
5 changed files with 710 additions and 192 deletions
+140 -7
View File
@@ -10,15 +10,40 @@ use uuid::Uuid;
/// Shape a Core JSON response into the format FIFA 23 expects for this path.
/// Returns the shaped response or the original unchanged if no shaping is defined.
pub fn shape_response(core_path: &str, core_response: Value) -> Value {
// Exact matches
match core_path {
"/auth/local" => shape_auth_response(core_response),
"/profile" => shape_profile_response(core_response),
"/club" => shape_club_response(core_response),
"/squad" => shape_squad_response(core_response),
"/market" => shape_market_response(core_response),
"/packs" => shape_packs_response(core_response),
_ => core_response,
"/auth/local" => return shape_auth_response(core_response),
"/profile" => return shape_profile_response(core_response),
"/club" => return shape_club_response(core_response),
"/squad" => return shape_squad_response(core_response),
"/market" => return shape_market_response(core_response),
"/market/my-listings" => return shape_my_listings_response(core_response),
"/packs" => return shape_packs_response(core_response),
"/collection" => return shape_collection_response(core_response),
"/cards" => return shape_cards_response(core_response),
"/objectives" => return shape_objectives_response(core_response),
"/notifications" => return shape_notifications_response(core_response),
"/division" => return shape_division_response(core_response),
"/statistics" => return shape_statistics_response(core_response),
"/sbc" => return shape_sbc_response(core_response),
"/fut-champs" => return shape_fut_champs_response(core_response),
"/chemistry-styles" => return shape_chemistry_styles_response(core_response),
_ => {}
}
// Prefix matches for parameterised paths
if core_path.starts_with("/packs/open/") {
return shape_pack_open_response(core_response);
}
if core_path.starts_with("/draft/sessions/") {
return shape_draft_response(core_response);
}
if core_path.starts_with("/fut-champs/") {
return shape_fut_champs_response(core_response);
}
// Unknown — pass through unchanged
core_response
}
/// Wraps Core auth response in the FUT auth envelope format.
@@ -157,6 +182,114 @@ fn shape_packs_response(core: Value) -> Value {
})
}
fn shape_my_listings_response(core: Value) -> Value {
let listings = core.get("listings").cloned().unwrap_or(json!([]));
json!({
"tradepile": listings,
"credits": 0,
"openfut": true,
})
}
fn shape_collection_response(core: Value) -> Value {
let items = core.get("collection").cloned().unwrap_or(json!([]));
let total = core.get("total").cloned().unwrap_or(json!(0));
json!({
"itemData": items,
"total": total,
"openfut": true,
})
}
fn shape_cards_response(core: Value) -> Value {
let cards = core.get("cards").cloned().unwrap_or(json!([]));
let total = core.get("total").cloned().unwrap_or(json!(0));
json!({
"items": cards,
"total": total,
"openfut": true,
})
}
fn shape_objectives_response(core: Value) -> Value {
let objectives = core.get("objectives").cloned().unwrap_or(json!([]));
json!({
"objectives": objectives,
"openfut": true,
})
}
fn shape_notifications_response(core: Value) -> Value {
let notifications = core.get("notifications").cloned().unwrap_or(json!([]));
let count = core.get("count").cloned().unwrap_or(json!(0));
json!({
"notifications": notifications,
"count": count,
"openfut": true,
})
}
fn shape_division_response(core: Value) -> Value {
json!({
"divisionRivalsInfo": {
"division": core.get("division").cloned().unwrap_or(json!(5)),
"seasonNumber": core.get("season_number").cloned().unwrap_or(json!(1)),
"points": core.get("season_points").cloned().unwrap_or(json!(0)),
"matchesPlayed": core.get("matches_played").cloned().unwrap_or(json!(0)),
"matchesRemaining": core.get("matches_remaining").cloned().unwrap_or(json!(10)),
"record": core.get("record").cloned().unwrap_or(json!({})),
},
"openfut": true,
"_raw": core,
})
}
fn shape_statistics_response(core: Value) -> Value {
json!({
"stats": core,
"openfut": true,
})
}
fn shape_sbc_response(core: Value) -> Value {
let sbcs = core.get("sbcs").cloned().unwrap_or(json!([]));
json!({
"challenges": sbcs,
"openfut": true,
})
}
fn shape_pack_open_response(core: Value) -> Value {
let cards = core.get("cards").cloned().unwrap_or(json!([]));
json!({
"itemData": cards,
"duplicateItemIdList": [],
"openfut": true,
})
}
fn shape_draft_response(core: Value) -> Value {
json!({
"draft": core,
"openfut": true,
})
}
fn shape_fut_champs_response(core: Value) -> Value {
json!({
"futChampions": core,
"openfut": true,
})
}
fn shape_chemistry_styles_response(core: Value) -> Value {
let styles = core.get("chemistry_styles").cloned().unwrap_or(json!([]));
json!({
"chemistryStyles": styles,
"openfut": true,
})
}
#[cfg(test)]
mod tests {
use super::*;