Skip to main content

game_manager_lib/services/integration/pcgamingwiki/
parsers.rs

1//! Funções auxiliares de extração e normalização de dados da API do PCGamingWiki.
2//!
3//! Todos os parsers operam sobre o JSON bruto retornado pela `cargoquery` e
4//! são usados exclusivamente por [`crate::services::integration::pcgamingwiki::fetch`].
5
6use serde_json::Value;
7
8type L10nLists = (
9    Option<Vec<String>>,
10    Option<Vec<String>>,
11    Option<Vec<String>>,
12);
13
14/// Agrupa os resultados da tabela L10n (uma linha por idioma) em três listas:
15/// idiomas com suporte a interface, áudio e legendas.
16///
17/// Filtra valores negativos ("false", "n/a", "unknown") — só inclui idiomas
18/// confirmados ("true") ou com tradução de fã ("hackable").
19pub(crate) fn parse_l10n_rows(rows: &[Value]) -> L10nLists {
20    let mut interface_langs = Vec::new();
21    let mut audio_langs = Vec::new();
22    let mut subtitle_langs = Vec::new();
23
24    for row in rows {
25        let title = match row.get("title") {
26            Some(t) => t,
27            None => continue,
28        };
29
30        let language = match title.get("language").and_then(|v| v.as_str()) {
31            Some(l) if !l.is_empty() => l.to_string(),
32            _ => continue,
33        };
34
35        let is_supported = |key: &str| -> bool {
36            title
37                .get(key)
38                .and_then(|v| v.as_str())
39                .map(|s| s == "true" || s == "hackable")
40                .unwrap_or(false)
41        };
42
43        if is_supported("interface") {
44            interface_langs.push(language.clone());
45        }
46        if is_supported("audio") {
47            audio_langs.push(language.clone());
48        }
49        if is_supported("subtitles") {
50            subtitle_langs.push(language.clone());
51        }
52    }
53
54    (
55        if interface_langs.is_empty() {
56            None
57        } else {
58            Some(interface_langs)
59        },
60        if audio_langs.is_empty() {
61            None
62        } else {
63            Some(audio_langs)
64        },
65        if subtitle_langs.is_empty() {
66            None
67        } else {
68            Some(subtitle_langs)
69        },
70    )
71}
72
73/// Extrai um campo `title.<key>` do primeiro resultado de cargoquery.
74pub(crate) fn extract_field(rows: &[Value], key: &str) -> Option<String> {
75    rows.first()
76        .and_then(|row| row.get("title"))
77        .and_then(|title| title.get(key))
78        .and_then(|v| v.as_str())
79        .map(|s| s.trim().to_string())
80        .filter(|s| !s.is_empty())
81}
82
83/// Normaliza valores booleanos da PCGW para string canônica.
84///
85/// A API retorna strings como "true", "false", "hackable", "unknown", "n/a".
86/// Preserva como string para o frontend tratar conforme contexto.
87pub(crate) fn normalize_bool_field(value: Option<String>) -> Option<String> {
88    value.map(|v| v.to_lowercase()).filter(|v| !v.is_empty())
89}