Skip to main content

loadsmith_thunderstore/
registry.rs

1use std::pin::Pin;
2
3use loadsmith_core::{PackageId, PackageRef};
4use loadsmith_registry::{Registry, ResolvedVersion, VersionInfo};
5
6use crate::{Error, in_memory::InMemoryIndex, sqlite::SqliteIndex};
7
8/// A registry that wraps a thunderstore package index.
9///
10/// Supports both in-memory and SQLite-backed index backends and implements
11/// the [`Registry`] trait for mod dependency resolution.
12///
13/// # Examples
14///
15/// ```
16/// use loadsmith_thunderstore::ThunderstoreRegistry;
17///
18/// let registry = ThunderstoreRegistry::default();
19/// ```
20#[derive(Debug, Clone)]
21pub struct ThunderstoreRegistry {
22    _client: thunderstore::Client,
23    index: Index,
24}
25
26#[derive(Debug, Clone)]
27enum Index {
28    InMemory(InMemoryIndex),
29    Sqlite(SqliteIndex),
30}
31
32impl ThunderstoreRegistry {
33    fn new(client: thunderstore::Client, index: Index) -> Self {
34        Self {
35            _client: client,
36            index,
37        }
38    }
39
40    /// Creates a registry backed by an in-memory index.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use loadsmith_thunderstore::{ThunderstoreRegistry, in_memory::InMemoryIndex};
46    /// use thunderstore::Client;
47    ///
48    /// let client = Client::new();
49    /// let index = InMemoryIndex::new(client.clone());
50    /// let registry = ThunderstoreRegistry::in_memory(client, index);
51    /// ```
52    pub fn in_memory(client: thunderstore::Client, index: InMemoryIndex) -> Self {
53        Self::new(client, Index::InMemory(index))
54    }
55
56    /// Creates a registry backed by a SQLite index.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use loadsmith_thunderstore::{ThunderstoreRegistry, sqlite::SqliteIndex};
62    /// use thunderstore::Client;
63    ///
64    /// let client = Client::new();
65    /// let db = rusqlite::Connection::open_in_memory().unwrap();
66    /// let index = SqliteIndex::connect(client.clone(), db).unwrap();
67    /// let registry = ThunderstoreRegistry::sqlite(client, index);
68    /// ```
69    pub fn sqlite(client: thunderstore::Client, index: SqliteIndex) -> Self {
70        Self::new(client, Index::Sqlite(index))
71    }
72
73    /// Fetches the package index for a thunderstore community.
74    ///
75    /// The community string corresponds to a thunderstore community slug
76    /// (e.g. `"rounds"`, `"lethal-company"`).
77    ///
78    /// # Examples
79    ///
80    /// ```no_run
81    /// use loadsmith_thunderstore::ThunderstoreRegistry;
82    ///
83    /// let rt = tokio::runtime::Runtime::new().unwrap();
84    /// rt.block_on(async {
85    ///     let registry = ThunderstoreRegistry::default();
86    ///     registry.update("rounds").await.unwrap();
87    /// });
88    /// ```
89    pub async fn update(&self, community: impl Into<String>) -> Result<(), Error> {
90        match &self.index {
91            Index::InMemory(in_memory_index) => in_memory_index.update(community).await,
92            Index::Sqlite(sqlite_index) => sqlite_index.update(community.into()).await,
93        }
94    }
95}
96
97impl Default for ThunderstoreRegistry {
98    fn default() -> Self {
99        let client = thunderstore::Client::default();
100        Self::in_memory(client.clone(), InMemoryIndex::new(client))
101    }
102}
103
104impl Registry for ThunderstoreRegistry {
105    fn version_info<'a>(
106        &'a self,
107        id: &'a PackageId,
108        _metadata: Option<&'a serde_json::Value>,
109    ) -> Pin<Box<dyn Future<Output = loadsmith_registry::Result<Vec<VersionInfo>>> + 'a>> {
110        Box::pin(async move {
111            match &self.index {
112                Index::InMemory(in_memory_index) => in_memory_index.version_info(id).await,
113                Index::Sqlite(sqlite_index) => sqlite_index.version_info(id),
114            }
115            .map_err(loadsmith_registry::Error::other)?
116            .ok_or(loadsmith_registry::Error::PackageNotFound)
117        })
118    }
119
120    fn resolve<'a>(
121        &'a self,
122        ref_: &'a PackageRef,
123        _metadata: Option<&'a serde_json::Value>,
124    ) -> Pin<Box<dyn Future<Output = loadsmith_registry::Result<ResolvedVersion>> + 'a>> {
125        Box::pin(async move {
126            match &self.index {
127                Index::InMemory(in_memory_index) => in_memory_index.resolve(ref_).await,
128                Index::Sqlite(sqlite_index) => sqlite_index.resolve(ref_),
129            }
130            .map_err(loadsmith_registry::Error::other)?
131            .ok_or(loadsmith_registry::Error::VersionNotFound)
132        })
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[tokio::test]
141    #[ignore = "requires network access"]
142    async fn get_package() {
143        let registry = ThunderstoreRegistry::default();
144        registry.update("rounds").await.unwrap();
145
146        let package = PackageId::new("BepInEx-BepInExPack_ROUNDS");
147
148        let versions = registry.version_info(&package, None).await.unwrap();
149
150        println!("{versions:#?}");
151    }
152}