Skip to main content

romm_cli/core/
resolve.rs

1//! Shared name/slug → ID resolution for platforms and collections.
2
3use anyhow::{anyhow, Result};
4
5use crate::client::RommClient;
6use crate::endpoints::collections::{ListCollections, ListSmartCollections};
7use crate::endpoints::platforms::ListPlatforms;
8use crate::types::{Collection, Platform};
9
10/// Resolves a platform ID from a string query by matching against slugs, names, and custom names.
11pub fn resolve_platform_id_from_list(query: &str, platforms: &[Platform]) -> Result<u64> {
12    let normalized = query.trim().to_ascii_lowercase();
13
14    if let Some(platform) = platforms.iter().find(|p| {
15        p.slug.eq_ignore_ascii_case(&normalized) || p.fs_slug.eq_ignore_ascii_case(&normalized)
16    }) {
17        return Ok(platform.id);
18    }
19
20    let exact_name_matches: Vec<&Platform> = platforms
21        .iter()
22        .filter(|p| {
23            p.name.eq_ignore_ascii_case(&normalized)
24                || p.display_name
25                    .as_deref()
26                    .is_some_and(|name| name.eq_ignore_ascii_case(&normalized))
27                || p.custom_name
28                    .as_deref()
29                    .is_some_and(|name| name.eq_ignore_ascii_case(&normalized))
30        })
31        .collect();
32
33    match exact_name_matches.len() {
34        1 => Ok(exact_name_matches[0].id),
35        0 => Err(anyhow!(
36            "No platform found for '{}'. Use 'romm-cli platforms list' to inspect available values.",
37            query
38        )),
39        _ => {
40            let names = exact_name_matches
41                .iter()
42                .map(|p| format!("{} ({})", p.name, p.id))
43                .collect::<Vec<_>>()
44                .join(", ");
45            Err(anyhow!(
46                "Platform '{}' is ambiguous. Matches: {}. Please use a more specific --platform value.",
47                query,
48                names
49            ))
50        }
51    }
52}
53
54/// Resolves a platform query (slug or name) to a numeric ID.
55pub async fn resolve_platform_id(
56    client: &RommClient,
57    platform_query: Option<&str>,
58) -> Result<Option<u64>> {
59    let Some(query) = platform_query.map(str::trim).filter(|q| !q.is_empty()) else {
60        return Ok(None);
61    };
62    let platforms = client.call(&ListPlatforms).await?;
63    resolve_platform_id_from_list(query, &platforms).map(Some)
64}
65
66/// Resolves multiple platform queries to a list of unique numeric IDs.
67pub async fn resolve_platform_ids(client: &RommClient, names: &[String]) -> Result<Vec<u64>> {
68    if names.is_empty() {
69        return Ok(Vec::new());
70    }
71    let platforms = client.call(&ListPlatforms).await?;
72    let mut out = Vec::new();
73    for n in names {
74        let id = resolve_platform_id_from_list(n.trim(), &platforms)?;
75        if !out.contains(&id) {
76            out.push(id);
77        }
78    }
79    Ok(out)
80}
81
82fn match_collections_by_name<'a>(q: &str, collections: &'a [Collection]) -> Vec<&'a Collection> {
83    let n = q.trim().to_ascii_lowercase();
84    collections
85        .iter()
86        .filter(|c| c.name.eq_ignore_ascii_case(&n))
87        .collect()
88}
89
90/// Resolves a manual collection by ID or exact name.
91pub async fn resolve_manual_collection_id(
92    client: &RommClient,
93    query: Option<&str>,
94) -> Result<Option<u64>> {
95    let Some(q) = query.map(str::trim).filter(|s| !s.is_empty()) else {
96        return Ok(None);
97    };
98    if let Ok(id) = q.parse::<u64>() {
99        return Ok(Some(id));
100    }
101    let list = client.call(&ListCollections).await?.into_vec();
102    let matches = match_collections_by_name(q, &list);
103    match matches.len() {
104        0 => Err(anyhow!(
105            "No manual collection named '{}'. Use `romm-cli collections list`.",
106            q
107        )),
108        1 => Ok(Some(matches[0].id)),
109        _ => Err(anyhow!(
110            "Manual collection '{}' is ambiguous among {} matches; use a numeric id.",
111            q,
112            matches.len()
113        )),
114    }
115}
116
117/// Resolves a smart collection by ID or exact name.
118pub async fn resolve_smart_collection_id(
119    client: &RommClient,
120    query: Option<&str>,
121) -> Result<Option<u64>> {
122    let Some(q) = query.map(str::trim).filter(|s| !s.is_empty()) else {
123        return Ok(None);
124    };
125    if let Ok(id) = q.parse::<u64>() {
126        return Ok(Some(id));
127    }
128    let list = client.call(&ListSmartCollections).await?.into_vec();
129    let matches = match_collections_by_name(q, &list);
130    match matches.len() {
131        0 => Err(anyhow!(
132            "No smart collection named '{}'. Use `romm-cli collections list`.",
133            q
134        )),
135        1 => Ok(Some(matches[0].id)),
136        _ => Err(anyhow!(
137            "Smart collection '{}' is ambiguous among {} matches; use a numeric id.",
138            q,
139            matches.len()
140        )),
141    }
142}