use std::collections::HashMap;
use crate::error::Result;
use crate::internal::http::{build_http_client, fetch_items_internal};
use crate::models::{Language, Platform};
use super::Item;
#[derive(Debug)]
pub struct ItemIndex {
items: Vec<Item>,
by_id: HashMap<String, usize>,
by_slug: HashMap<String, usize>,
}
impl ItemIndex {
pub async fn fetch() -> Result<Self> {
Self::fetch_with_config(Platform::Pc, Language::English, true).await
}
pub async fn fetch_with_config(
platform: Platform,
language: Language,
crossplay: bool,
) -> Result<Self> {
let http = build_http_client(platform, language, crossplay)
.map_err(crate::error::Error::Network)?;
let items = fetch_items_internal(&http).await?;
Ok(Self::new(items))
}
pub fn new(items: Vec<Item>) -> Self {
let mut by_id = HashMap::with_capacity(items.len());
let mut by_slug = HashMap::with_capacity(items.len());
for (idx, item) in items.iter().enumerate() {
by_id.insert(item.id.clone(), idx);
by_slug.insert(item.slug.clone(), idx);
}
Self {
items,
by_id,
by_slug,
}
}
pub fn get_by_id(&self, id: &str) -> Option<&Item> {
self.by_id.get(id).and_then(|&idx| self.items.get(idx))
}
pub fn get_by_slug(&self, slug: &str) -> Option<&Item> {
self.by_slug.get(slug).and_then(|&idx| self.items.get(idx))
}
pub fn iter(&self) -> impl Iterator<Item = &Item> {
self.items.iter()
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn as_slice(&self) -> &[Item] {
&self.items
}
pub fn into_items(self) -> Vec<Item> {
self.items
}
}
impl Default for ItemIndex {
fn default() -> Self {
Self::new(Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::item::ItemTranslation;
use std::collections::HashMap as StdHashMap;
fn make_test_item(id: &str, slug: &str, name: &str) -> Item {
Item {
id: id.to_string(),
slug: slug.to_string(),
game_ref: None,
tradable: Some(true),
tags: vec![],
i18n: StdHashMap::from([(
"en".to_string(),
ItemTranslation {
name: name.to_string(),
icon: "/icons/test.png".to_string(),
thumb: None,
sub_icon: None,
description: None,
wiki_link: None,
},
)]),
rarity: None,
vaulted: None,
ducats: None,
trading_tax: None,
mastery_rank: None,
max_rank: None,
max_charges: None,
max_amber_stars: None,
max_cyan_stars: None,
base_endo: None,
endo_multiplier: None,
set_root: None,
set_parts: None,
quantity_in_set: None,
bulk_tradable: None,
subtypes: None,
}
}
#[test]
fn test_item_index_new() {
let items = vec![
make_test_item("id-1", "slug-1", "Item One"),
make_test_item("id-2", "slug-2", "Item Two"),
];
let index = ItemIndex::new(items);
assert_eq!(index.len(), 2);
assert!(!index.is_empty());
}
#[test]
fn test_item_index_get_by_id() {
let items = vec![
make_test_item("id-1", "slug-1", "Item One"),
make_test_item("id-2", "slug-2", "Item Two"),
];
let index = ItemIndex::new(items);
let item = index.get_by_id("id-1").expect("Should find item");
assert_eq!(item.name(), "Item One");
let item = index.get_by_id("id-2").expect("Should find item");
assert_eq!(item.name(), "Item Two");
assert!(index.get_by_id("nonexistent").is_none());
}
#[test]
fn test_item_index_get_by_slug() {
let items = vec![
make_test_item("id-1", "slug-1", "Item One"),
make_test_item("id-2", "slug-2", "Item Two"),
];
let index = ItemIndex::new(items);
let item = index.get_by_slug("slug-1").expect("Should find item");
assert_eq!(item.name(), "Item One");
let item = index.get_by_slug("slug-2").expect("Should find item");
assert_eq!(item.name(), "Item Two");
assert!(index.get_by_slug("nonexistent").is_none());
}
#[test]
fn test_item_index_iter() {
let items = vec![
make_test_item("id-1", "slug-1", "Item One"),
make_test_item("id-2", "slug-2", "Item Two"),
];
let index = ItemIndex::new(items);
let names: Vec<&str> = index.iter().map(|i| i.name()).collect();
assert_eq!(names, vec!["Item One", "Item Two"]);
}
#[test]
fn test_item_index_empty() {
let index = ItemIndex::default();
assert!(index.is_empty());
assert_eq!(index.len(), 0);
assert!(index.get_by_id("any").is_none());
assert!(index.get_by_slug("any").is_none());
}
}