77af7ce3c9
- Division tab: leaderboard table (10 clubs, color-coded promo/relegation rows, player row highlighted, sorted by pts) - Market tab: trade history table (last 30 buy/sell events, card name/OVR/price/date) loaded alongside market listings and my listings - Mapper: added GET /division/leaderboard and GET /trade/history exact routes - Mapper assertion bumped to ≥61 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
809 lines
34 KiB
Rust
809 lines
34 KiB
Rust
/// Mapping layer: translates known FIFA 23 API paths to OpenFUT Core endpoints.
|
|
/// Unknown paths return None and will be recorded for reverse engineering.
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct CoreMapping {
|
|
pub method: &'static str,
|
|
pub core_path: String,
|
|
#[allow(dead_code)]
|
|
pub notes: &'static str,
|
|
}
|
|
|
|
// ── Exact mappings (static path, no dynamic segments) ─────────────────────────
|
|
|
|
struct ExactRoute {
|
|
ea_method: &'static str,
|
|
ea_path: &'static str,
|
|
core_method: &'static str,
|
|
core_path: &'static str,
|
|
notes: &'static str,
|
|
}
|
|
|
|
const EXACT: &[ExactRoute] = &[
|
|
// ── Auth ─────────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/auth",
|
|
core_method: "POST", core_path: "/auth/local",
|
|
notes: "FUT login → Core local auth",
|
|
},
|
|
// ── Profile / Settings ───────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/user/settings",
|
|
core_method: "GET", core_path: "/profile",
|
|
notes: "FUT user settings → Core profile",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "PUT", ea_path: "/ut/game/fut/user/settings",
|
|
core_method: "PUT", core_path: "/settings",
|
|
notes: "FUT update settings → Core settings",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/user/accountinfo",
|
|
core_method: "GET", core_path: "/profile",
|
|
notes: "FUT account info → Core profile",
|
|
},
|
|
// ── Club / Mass info ──────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/usermassinfo",
|
|
core_method: "GET", core_path: "/club",
|
|
notes: "FUT mass info (club + coins) → Core club",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/club",
|
|
core_method: "GET", core_path: "/club",
|
|
notes: "FUT club info → Core club",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "PUT", ea_path: "/ut/game/fut/club",
|
|
core_method: "PUT", core_path: "/club",
|
|
notes: "FUT update club → Core update club",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/club/stats",
|
|
core_method: "GET", core_path: "/statistics",
|
|
notes: "FUT club stats → Core statistics",
|
|
},
|
|
// ── Cards / Collection ────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/item",
|
|
core_method: "GET", core_path: "/collection",
|
|
notes: "FUT collection → Core owned cards",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/item/search",
|
|
core_method: "GET", core_path: "/cards",
|
|
notes: "FUT item search → Core card catalogue (query params forwarded)",
|
|
},
|
|
// ── Squad ─────────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/squad/active",
|
|
core_method: "GET", core_path: "/squad",
|
|
notes: "FUT active squad → Core squad",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/squad/0",
|
|
core_method: "GET", core_path: "/squad",
|
|
notes: "FUT squad by slot 0 → Core squad (first squad)",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "PUT", ea_path: "/ut/game/fut/squad/active",
|
|
core_method: "POST", core_path: "/squad",
|
|
notes: "FUT save squad → Core save squad",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/squad/chemistry",
|
|
core_method: "GET", core_path: "/squad",
|
|
notes: "FUT squad chemistry → Core squad (chemistry included in response)",
|
|
},
|
|
// ── Packs ─────────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/store/packdetails",
|
|
core_method: "GET", core_path: "/packs",
|
|
notes: "FUT pack store → Core pack list",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/store/purchase",
|
|
core_method: "POST", core_path: "/packs/buy",
|
|
notes: "FUT pack purchase → Core pack buy",
|
|
},
|
|
// ── Transfer Market ───────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/transfermarket",
|
|
core_method: "GET", core_path: "/market",
|
|
notes: "FUT transfer market search → Core NPC market",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/trade/bid",
|
|
core_method: "POST", core_path: "/market/buy",
|
|
notes: "FUT bid/buy now → Core market buy",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/trade/watchlist",
|
|
core_method: "GET", core_path: "/market",
|
|
notes: "FUT watchlist → Core market (approximation)",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/trade/tradepile",
|
|
core_method: "GET", core_path: "/market/my-listings",
|
|
notes: "FUT trade pile (my listings) → Core my-listings",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/auctionhouse",
|
|
core_method: "POST", core_path: "/market/sell",
|
|
notes: "FUT list card on AH → Core sell card",
|
|
},
|
|
// ── Objectives ────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/objectives",
|
|
core_method: "GET", core_path: "/objectives",
|
|
notes: "FUT objectives → Core objectives list",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/objectives/claim",
|
|
core_method: "POST", core_path: "/objectives/claim",
|
|
notes: "FUT claim objective → Core claim",
|
|
},
|
|
// ── Events ────────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/events",
|
|
core_method: "GET", core_path: "/events",
|
|
notes: "FUT events → Core events list",
|
|
},
|
|
// ── Matches / Squad Battles ───────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/squadbattle/opponent",
|
|
core_method: "GET", core_path: "/matches/opponent",
|
|
notes: "Squad battles opponent → Core match opponent generator",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/result",
|
|
core_method: "POST", core_path: "/matches/result",
|
|
notes: "FUT match result submit → Core match result",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/matches",
|
|
core_method: "GET", core_path: "/matches",
|
|
notes: "FUT match history → Core match list",
|
|
},
|
|
// ── SBC ──────────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/sbc",
|
|
core_method: "GET", core_path: "/sbc",
|
|
notes: "FUT SBC list → Core SBC list",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/sbc/challenges",
|
|
core_method: "GET", core_path: "/sbc",
|
|
notes: "FUT SBC challenges → Core SBC list",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/sbc/submission",
|
|
core_method: "POST", core_path: "/sbc/submit",
|
|
notes: "FUT SBC submission → Core SBC submit",
|
|
},
|
|
// ── Draft ─────────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/draft",
|
|
core_method: "GET", core_path: "/draft/squad",
|
|
notes: "FUT draft view → Core draft squad",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/draft/new",
|
|
core_method: "POST", core_path: "/draft/start",
|
|
notes: "FUT new draft → Core draft start",
|
|
},
|
|
// ── Division / Season ─────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/division/rivals",
|
|
core_method: "GET", core_path: "/division",
|
|
notes: "FUT Division Rivals status → Core division",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/division/rivals/claim",
|
|
core_method: "POST", core_path: "/rivals/claim-weekly",
|
|
notes: "FUT rivals weekly claim → Core rivals claim",
|
|
},
|
|
// ── FUT Champions ─────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/champs",
|
|
core_method: "GET", core_path: "/fut-champs",
|
|
notes: "FUT Champions status → Core fut-champs",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/champs/start",
|
|
core_method: "POST", core_path: "/fut-champs/start",
|
|
notes: "FUT Champions start week → Core fut-champs start",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/champs/history",
|
|
core_method: "GET", core_path: "/fut-champs/history",
|
|
notes: "FUT Champions history → Core history",
|
|
},
|
|
// ── Card Upgrades ─────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/chemistry",
|
|
core_method: "GET", core_path: "/chemistry-styles",
|
|
notes: "FUT chemistry styles → Core chemistry styles list",
|
|
},
|
|
// ── Notifications ─────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/notification",
|
|
core_method: "GET", core_path: "/notifications",
|
|
notes: "FUT notifications → Core notifications",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/notifications",
|
|
core_method: "GET", core_path: "/notifications",
|
|
notes: "FUT notifications (plural form) → Core notifications",
|
|
},
|
|
// ── Squad list ────────────────────────────────────────────────────────────
|
|
// ── Division / Season history / Leaderboard ───────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/division/history",
|
|
core_method: "GET", core_path: "/division/history",
|
|
notes: "FUT division history → Core season history",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/division/leaderboard",
|
|
core_method: "GET", core_path: "/division/leaderboard",
|
|
notes: "FUT division leaderboard → Core seeded NPC leaderboard",
|
|
},
|
|
// ── Market trade history ──────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/trade/history",
|
|
core_method: "GET", core_path: "/market/trade-history",
|
|
notes: "FUT trade history → Core market trade history",
|
|
},
|
|
// ── Daily check-in ────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/dailyObjective",
|
|
core_method: "GET", core_path: "/club/checkin",
|
|
notes: "FUT daily objective status → Core check-in status",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/dailyObjective/claim",
|
|
core_method: "POST", core_path: "/club/checkin",
|
|
notes: "FUT daily objective claim → Core check-in claim",
|
|
},
|
|
// ── Club milestones ───────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/milestones",
|
|
core_method: "GET", core_path: "/club/milestones",
|
|
notes: "FUT milestones → Core club milestones",
|
|
},
|
|
// ── Squad list ────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/squad/list",
|
|
core_method: "GET", core_path: "/squads",
|
|
notes: "FUT squad list → Core all squads",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "DELETE", ea_path: "/ut/game/fut/squad/active",
|
|
core_method: "DELETE", core_path: "/squad",
|
|
notes: "FUT delete active squad → Core delete squad (best-effort)",
|
|
},
|
|
// ── Achievements / Trophies ───────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/trophies",
|
|
core_method: "GET", core_path: "/achievements",
|
|
notes: "FUT trophies → Core achievements",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/trophy",
|
|
core_method: "GET", core_path: "/achievements",
|
|
notes: "FUT trophy (singular) → Core achievements",
|
|
},
|
|
// ── Rivals extra endpoints ────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/rivals/rank",
|
|
core_method: "GET", core_path: "/division",
|
|
notes: "FUT rivals rank → Core division",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/rivals/result",
|
|
core_method: "POST", core_path: "/matches/result",
|
|
notes: "FUT rivals match result → Core match result",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/rivals/leaderboard",
|
|
core_method: "GET", core_path: "/statistics",
|
|
notes: "FUT rivals leaderboard → Core statistics (offline approximation)",
|
|
},
|
|
// ── Objectives sub-groups ─────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/objectives/group",
|
|
core_method: "GET", core_path: "/objectives",
|
|
notes: "FUT objectives group → Core objectives",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/objectives/daily",
|
|
core_method: "GET", core_path: "/objectives",
|
|
notes: "FUT daily objectives → Core objectives",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/objectives/weekly",
|
|
core_method: "GET", core_path: "/objectives",
|
|
notes: "FUT weekly objectives → Core objectives",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/objectives/group/claim",
|
|
core_method: "POST", core_path: "/objectives/claim",
|
|
notes: "FUT objectives group claim → Core objectives claim",
|
|
},
|
|
// ── Catalogue / Card search ───────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/catalogue/item",
|
|
core_method: "GET", core_path: "/cards",
|
|
notes: "FUT catalogue items → Core card catalogue",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/catalogue",
|
|
core_method: "GET", core_path: "/cards",
|
|
notes: "FUT catalogue → Core card catalogue",
|
|
},
|
|
// ── Store / Pricing ───────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/store/pricetiers",
|
|
core_method: "GET", core_path: "/packs/store",
|
|
notes: "FUT price tiers → Core pack store definitions",
|
|
},
|
|
// ── Consumables / Chemistry / Fitness ─────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/consumables",
|
|
core_method: "GET", core_path: "/chemistry-styles",
|
|
notes: "FUT consumables → Core chemistry styles (approximation)",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "POST", ea_path: "/ut/game/fut/fitness",
|
|
core_method: "GET", core_path: "/club",
|
|
notes: "FUT fitness apply → Core club (placeholder, fitness not tracked)",
|
|
},
|
|
// ── Loan items ────────────────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/loanitems",
|
|
core_method: "GET", core_path: "/collection",
|
|
notes: "FUT loan items → Core collection (client filters is_loan)",
|
|
},
|
|
// ── Customization / Kit ───────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/customization",
|
|
core_method: "GET", core_path: "/settings",
|
|
notes: "FUT customization → Core settings",
|
|
},
|
|
ExactRoute {
|
|
ea_method: "PUT", ea_path: "/ut/game/fut/customization",
|
|
core_method: "PUT", core_path: "/settings",
|
|
notes: "FUT save customization → Core save settings",
|
|
},
|
|
// ── Active messages / MOTD ────────────────────────────────────────────────
|
|
ExactRoute {
|
|
ea_method: "GET", ea_path: "/ut/game/fut/activeMessage",
|
|
core_method: "GET", core_path: "/notifications",
|
|
notes: "FUT active messages → Core notifications (mapped to nearest equivalent)",
|
|
},
|
|
];
|
|
|
|
// ── Prefix mappings (dynamic path segments after a known prefix) ───────────────
|
|
|
|
struct PrefixRoute {
|
|
ea_method: &'static str,
|
|
ea_prefix: &'static str,
|
|
/// Generates the core path given the path suffix after ea_prefix.
|
|
core_path_fn: fn(&str) -> String,
|
|
notes: &'static str,
|
|
}
|
|
|
|
fn prefix_routes() -> &'static [PrefixRoute] {
|
|
&[
|
|
// /ut/game/fut/draft/{session_id} → GET /draft/sessions/{session_id}
|
|
PrefixRoute {
|
|
ea_method: "GET",
|
|
ea_prefix: "/ut/game/fut/draft/",
|
|
core_path_fn: |suffix| format!("/draft/sessions/{suffix}"),
|
|
notes: "FUT draft session → Core draft session",
|
|
},
|
|
// /ut/game/fut/draft/{id}/pick → POST /draft/sessions/{id}/pick
|
|
PrefixRoute {
|
|
ea_method: "POST",
|
|
ea_prefix: "/ut/game/fut/draft/",
|
|
core_path_fn: |suffix| {
|
|
// suffix is "{id}/pick" or "{id}/abandon"
|
|
let parts: Vec<&str> = suffix.splitn(2, '/').collect();
|
|
if parts.len() == 2 {
|
|
format!("/draft/sessions/{}/{}", parts[0], parts[1])
|
|
} else {
|
|
format!("/draft/sessions/{suffix}")
|
|
}
|
|
},
|
|
notes: "FUT draft pick/abandon → Core draft action",
|
|
},
|
|
// /ut/game/fut/champs/{session_id}/result → POST /fut-champs/{session_id}/result
|
|
PrefixRoute {
|
|
ea_method: "POST",
|
|
ea_prefix: "/ut/game/fut/champs/",
|
|
core_path_fn: |suffix| format!("/fut-champs/{suffix}"),
|
|
notes: "FUT Champions match result / claim → Core fut-champs action",
|
|
},
|
|
// /ut/game/fut/sbc/set/{set_id}/submission → POST /sbc/submit
|
|
PrefixRoute {
|
|
ea_method: "POST",
|
|
ea_prefix: "/ut/game/fut/sbc/set/",
|
|
core_path_fn: |_| "/sbc/submit".to_string(),
|
|
notes: "FUT SBC set submission → Core SBC submit",
|
|
},
|
|
// /ut/game/fut/trade/{trade_id} (DELETE) → DELETE /market/listings/{trade_id}
|
|
PrefixRoute {
|
|
ea_method: "DELETE",
|
|
ea_prefix: "/ut/game/fut/trade/",
|
|
core_path_fn: |suffix| format!("/market/listings/{suffix}"),
|
|
notes: "FUT cancel trade listing → Core delete market listing",
|
|
},
|
|
// /ut/game/fut/item/{owned_id} (DELETE) → DELETE /collection/{owned_id}
|
|
PrefixRoute {
|
|
ea_method: "DELETE",
|
|
ea_prefix: "/ut/game/fut/item/",
|
|
core_path_fn: |suffix| format!("/collection/{suffix}"),
|
|
notes: "FUT quick-sell item → Core delete owned card",
|
|
},
|
|
// /ut/game/fut/item/{owned_id} (PUT) → PUT /collection/{owned_id}/chemistry
|
|
PrefixRoute {
|
|
ea_method: "PUT",
|
|
ea_prefix: "/ut/game/fut/item/",
|
|
core_path_fn: |suffix| {
|
|
// suffix is just the owned_card_id; game sends chemistry style in body
|
|
format!("/collection/{suffix}/chemistry")
|
|
},
|
|
notes: "FUT apply chemistry style → Core update card chemistry",
|
|
},
|
|
// /ut/game/fut/squad/{id} (GET) → GET /squads/{id}
|
|
// Note: must come after exact matches for /squad/active, /squad/0, etc.
|
|
PrefixRoute {
|
|
ea_method: "GET",
|
|
ea_prefix: "/ut/game/fut/squad/",
|
|
core_path_fn: |suffix| {
|
|
// Skip known non-ID suffixes already handled as exact routes
|
|
match suffix {
|
|
"active" | "chemistry" | "list" => "/squad".to_string(),
|
|
_ => format!("/squads/{suffix}"),
|
|
}
|
|
},
|
|
notes: "FUT squad by ID → Core squad by ID",
|
|
},
|
|
// /ut/game/fut/squad/{id} (DELETE) → DELETE /squads/{id}
|
|
PrefixRoute {
|
|
ea_method: "DELETE",
|
|
ea_prefix: "/ut/game/fut/squad/",
|
|
core_path_fn: |suffix| format!("/squads/{suffix}"),
|
|
notes: "FUT delete squad by ID → Core delete squad",
|
|
},
|
|
// /ut/game/fut/sbc/set/{set_id} (GET) → GET /sbc/{set_id}
|
|
PrefixRoute {
|
|
ea_method: "GET",
|
|
ea_prefix: "/ut/game/fut/sbc/set/",
|
|
core_path_fn: |suffix| format!("/sbc/{suffix}"),
|
|
notes: "FUT SBC set details → Core individual SBC",
|
|
},
|
|
// /ut/game/fut/store/pack/{pack_id}/open → POST /packs/open/{pack_id}
|
|
PrefixRoute {
|
|
ea_method: "POST",
|
|
ea_prefix: "/ut/game/fut/store/pack/",
|
|
core_path_fn: |suffix| {
|
|
// suffix: "{pack_id}/open" or just "{pack_id}"
|
|
let id = suffix.trim_end_matches("/open");
|
|
format!("/packs/open/{id}")
|
|
},
|
|
notes: "FUT open pack → Core open pack",
|
|
},
|
|
]
|
|
}
|
|
|
|
// ── Public API ────────────────────────────────────────────────────────────────
|
|
|
|
/// Attempt to map an incoming FIFA 23 request to an OpenFUT Core call.
|
|
/// Returns Some(mapping) if the route is known, None otherwise.
|
|
pub fn map_to_core(method: &str, path: &str) -> Option<CoreMapping> {
|
|
let m = method.to_uppercase();
|
|
let p = path.trim_end_matches('/');
|
|
|
|
// 1. Exact match
|
|
for r in EXACT {
|
|
if r.ea_method == m && r.ea_path == p {
|
|
return Some(CoreMapping {
|
|
method: r.core_method,
|
|
core_path: r.core_path.to_string(),
|
|
notes: r.notes,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 2. Prefix match — first prefix that fits wins
|
|
for r in prefix_routes() {
|
|
if r.ea_method == m {
|
|
if let Some(suffix) = p.strip_prefix(r.ea_prefix) {
|
|
if !suffix.is_empty() {
|
|
let core_path = (r.core_path_fn)(suffix);
|
|
return Some(CoreMapping {
|
|
method: "GET", // overridden per-route below
|
|
core_path,
|
|
notes: r.notes,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Same as `map_to_core` but preserves the original HTTP method for prefix routes.
|
|
///
|
|
/// The simple `map_to_core` hardcodes "GET" for prefix routes because the method
|
|
/// is already known from the match condition. This helper re-derives it.
|
|
pub fn map_to_core_with_method(method: &str, path: &str) -> Option<CoreMapping> {
|
|
let m = method.to_uppercase();
|
|
let p = path.trim_end_matches('/');
|
|
|
|
for r in EXACT {
|
|
if r.ea_method == m && r.ea_path == p {
|
|
return Some(CoreMapping {
|
|
method: r.core_method,
|
|
core_path: r.core_path.to_string(),
|
|
notes: r.notes,
|
|
});
|
|
}
|
|
}
|
|
|
|
for r in prefix_routes() {
|
|
if r.ea_method == m {
|
|
if let Some(suffix) = p.strip_prefix(r.ea_prefix) {
|
|
if !suffix.is_empty() {
|
|
return Some(CoreMapping {
|
|
method: r.ea_method, // use the matched method
|
|
core_path: (r.core_path_fn)(suffix),
|
|
notes: r.notes,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Return a safe placeholder response for unknown endpoints.
|
|
pub fn placeholder_response(method: &str, path: &str) -> serde_json::Value {
|
|
tracing::warn!(
|
|
"UNKNOWN ENDPOINT: {} {} — returning placeholder",
|
|
method,
|
|
path
|
|
);
|
|
serde_json::json!({
|
|
"status": "ok",
|
|
"openfut_note": "This endpoint has not been mapped yet. Check captures/ for details.",
|
|
"method": method,
|
|
"path": path,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_known_auth_maps() {
|
|
let m = map_to_core("POST", "/ut/auth");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/auth/local");
|
|
}
|
|
|
|
#[test]
|
|
fn test_known_market_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/transfermarket");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/market");
|
|
}
|
|
|
|
#[test]
|
|
fn test_events_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/events");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/events");
|
|
}
|
|
|
|
#[test]
|
|
fn test_squad_put_maps() {
|
|
let m = map_to_core("PUT", "/ut/game/fut/squad/active");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().method, "POST");
|
|
}
|
|
|
|
#[test]
|
|
fn test_unknown_returns_none() {
|
|
assert!(map_to_core("GET", "/ut/game/fut/some/unknown/path").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_trailing_slash_trimmed() {
|
|
let m = map_to_core("POST", "/ut/auth/");
|
|
assert!(m.is_some());
|
|
}
|
|
|
|
// ── New exact mappings ────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn test_sbc_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/sbc");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/sbc");
|
|
}
|
|
|
|
#[test]
|
|
fn test_division_rivals_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/division/rivals");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/division");
|
|
}
|
|
|
|
#[test]
|
|
fn test_fut_champs_start_maps() {
|
|
let m = map_to_core("POST", "/ut/game/fut/champs/start");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/fut-champs/start");
|
|
}
|
|
|
|
#[test]
|
|
fn test_notifications_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/notification");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/notifications");
|
|
}
|
|
|
|
#[test]
|
|
fn test_settings_put_maps() {
|
|
let m = map_to_core("PUT", "/ut/game/fut/user/settings");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/settings");
|
|
}
|
|
|
|
#[test]
|
|
fn test_item_search_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/item/search");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/cards");
|
|
}
|
|
|
|
#[test]
|
|
fn test_tradepile_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/trade/tradepile");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/market/my-listings");
|
|
}
|
|
|
|
// ── Prefix-based mappings ─────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn test_draft_session_get_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/draft/abc-123");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/draft/sessions/abc-123");
|
|
}
|
|
|
|
#[test]
|
|
fn test_draft_pick_maps() {
|
|
let m = map_to_core("POST", "/ut/game/fut/draft/abc-123/pick");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/draft/sessions/abc-123/pick");
|
|
}
|
|
|
|
#[test]
|
|
fn test_draft_abandon_maps() {
|
|
let m = map_to_core("POST", "/ut/game/fut/draft/abc-123/abandon");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/draft/sessions/abc-123/abandon");
|
|
}
|
|
|
|
#[test]
|
|
fn test_champs_result_maps() {
|
|
let m = map_to_core("POST", "/ut/game/fut/champs/sess-456/result");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/fut-champs/sess-456/result");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pack_open_maps() {
|
|
let m = map_to_core("POST", "/ut/game/fut/store/pack/pack-789/open");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/packs/open/pack-789");
|
|
}
|
|
|
|
#[test]
|
|
fn test_quick_sell_maps() {
|
|
let m = map_to_core("DELETE", "/ut/game/fut/item/owned-111");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/collection/owned-111");
|
|
}
|
|
|
|
#[test]
|
|
fn test_cancel_listing_maps() {
|
|
let m = map_to_core("DELETE", "/ut/game/fut/trade/trade-222");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/market/listings/trade-222");
|
|
}
|
|
|
|
#[test]
|
|
fn test_total_exact_routes_count() {
|
|
assert!(EXACT.len() >= 61, "expected at least 61 exact mappings, got {}", EXACT.len());
|
|
}
|
|
|
|
// ── Phase 22 new mappings ─────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn test_squad_list_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/squad/list");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/squads");
|
|
}
|
|
|
|
#[test]
|
|
fn test_squad_by_id_get_maps() {
|
|
let m = map_to_core_with_method("GET", "/ut/game/fut/squad/abc-squad-id");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/squads/abc-squad-id");
|
|
}
|
|
|
|
#[test]
|
|
fn test_squad_by_id_delete_maps() {
|
|
let m = map_to_core_with_method("DELETE", "/ut/game/fut/squad/abc-squad-id");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/squads/abc-squad-id");
|
|
}
|
|
|
|
#[test]
|
|
fn test_item_put_chemistry_maps() {
|
|
let m = map_to_core_with_method("PUT", "/ut/game/fut/item/owned-999");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/collection/owned-999/chemistry");
|
|
}
|
|
|
|
#[test]
|
|
fn test_sbc_set_get_maps() {
|
|
let m = map_to_core_with_method("GET", "/ut/game/fut/sbc/set/sbc-123");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/sbc/sbc-123");
|
|
}
|
|
|
|
#[test]
|
|
fn test_trophies_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/trophies");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/achievements");
|
|
}
|
|
|
|
#[test]
|
|
fn test_rivals_result_maps() {
|
|
let m = map_to_core("POST", "/ut/game/fut/rivals/result");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/matches/result");
|
|
}
|
|
|
|
#[test]
|
|
fn test_catalogue_item_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/catalogue/item");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/cards");
|
|
}
|
|
|
|
#[test]
|
|
fn test_objectives_daily_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/objectives/daily");
|
|
assert!(m.is_some());
|
|
assert_eq!(m.unwrap().core_path, "/objectives");
|
|
}
|
|
|
|
#[test]
|
|
fn test_active_message_maps() {
|
|
let m = map_to_core("GET", "/ut/game/fut/activeMessage");
|
|
assert!(m.is_some());
|
|
}
|
|
}
|