mockforge_plugin_registry/
api.rs

1//! Registry API client
2
3use crate::{
4    RegistryConfig, RegistryEntry, RegistryError, Result, SearchQuery, SearchResults, VersionEntry,
5};
6use serde::{Deserialize, Serialize};
7use std::time::Duration;
8
9/// Registry API client
10pub struct RegistryClient {
11    config: RegistryConfig,
12    client: reqwest::Client,
13}
14
15impl RegistryClient {
16    /// Create a new registry client
17    pub fn new(config: RegistryConfig) -> Result<Self> {
18        let client = reqwest::Client::builder()
19            .timeout(Duration::from_secs(config.timeout))
20            .build()
21            .map_err(|e| RegistryError::Network(e.to_string()))?;
22
23        Ok(Self { config, client })
24    }
25
26    /// Search for plugins
27    pub async fn search(&self, query: SearchQuery) -> Result<SearchResults> {
28        let url = format!("{}/api/v1/plugins/search", self.config.url);
29        let response = self
30            .client
31            .post(&url)
32            .json(&query)
33            .send()
34            .await
35            .map_err(|e| RegistryError::Network(e.to_string()))?;
36
37        if !response.status().is_success() {
38            return Err(RegistryError::Network(format!("Search failed: {}", response.status())));
39        }
40
41        let results = response.json().await.map_err(|e| RegistryError::Network(e.to_string()))?;
42
43        Ok(results)
44    }
45
46    /// Get plugin details
47    pub async fn get_plugin(&self, name: &str) -> Result<RegistryEntry> {
48        let url = format!("{}/api/v1/plugins/{}", self.config.url, name);
49        let response = self
50            .client
51            .get(&url)
52            .send()
53            .await
54            .map_err(|e| RegistryError::Network(e.to_string()))?;
55
56        if response.status() == reqwest::StatusCode::NOT_FOUND {
57            return Err(RegistryError::PluginNotFound(name.to_string()));
58        }
59
60        if !response.status().is_success() {
61            return Err(RegistryError::Network(format!(
62                "Get plugin failed: {}",
63                response.status()
64            )));
65        }
66
67        let entry = response.json().await.map_err(|e| RegistryError::Network(e.to_string()))?;
68
69        Ok(entry)
70    }
71
72    /// Get specific version of a plugin
73    pub async fn get_version(&self, name: &str, version: &str) -> Result<VersionEntry> {
74        let url = format!("{}/api/v1/plugins/{}/versions/{}", self.config.url, name, version);
75        let response = self
76            .client
77            .get(&url)
78            .send()
79            .await
80            .map_err(|e| RegistryError::Network(e.to_string()))?;
81
82        if !response.status().is_success() {
83            return Err(RegistryError::InvalidVersion(version.to_string()));
84        }
85
86        let entry = response.json().await.map_err(|e| RegistryError::Network(e.to_string()))?;
87
88        Ok(entry)
89    }
90
91    /// Publish a new plugin version
92    pub async fn publish(&self, manifest: PublishRequest) -> Result<PublishResponse> {
93        let token = self.config.token.as_ref().ok_or(RegistryError::AuthRequired)?;
94
95        let url = format!("{}/api/v1/plugins/publish", self.config.url);
96        let response = self
97            .client
98            .post(&url)
99            .bearer_auth(token)
100            .json(&manifest)
101            .send()
102            .await
103            .map_err(|e| RegistryError::Network(e.to_string()))?;
104
105        if response.status() == reqwest::StatusCode::UNAUTHORIZED {
106            return Err(RegistryError::AuthRequired);
107        }
108
109        if response.status() == reqwest::StatusCode::FORBIDDEN {
110            return Err(RegistryError::PermissionDenied);
111        }
112
113        if !response.status().is_success() {
114            return Err(RegistryError::Network(format!("Publish failed: {}", response.status())));
115        }
116
117        let result = response.json().await.map_err(|e| RegistryError::Network(e.to_string()))?;
118
119        Ok(result)
120    }
121
122    /// Download plugin binary
123    pub async fn download(&self, url: &str) -> Result<Vec<u8>> {
124        let response = self
125            .client
126            .get(url)
127            .send()
128            .await
129            .map_err(|e| RegistryError::Network(e.to_string()))?;
130
131        if !response.status().is_success() {
132            return Err(RegistryError::Network(format!("Download failed: {}", response.status())));
133        }
134
135        let bytes = response.bytes().await.map_err(|e| RegistryError::Network(e.to_string()))?;
136
137        Ok(bytes.to_vec())
138    }
139
140    /// Yank a plugin version (remove from index)
141    pub async fn yank(&self, name: &str, version: &str) -> Result<()> {
142        let token = self.config.token.as_ref().ok_or(RegistryError::AuthRequired)?;
143
144        let url = format!("{}/api/v1/plugins/{}/versions/{}/yank", self.config.url, name, version);
145        let response = self
146            .client
147            .delete(&url)
148            .bearer_auth(token)
149            .send()
150            .await
151            .map_err(|e| RegistryError::Network(e.to_string()))?;
152
153        if !response.status().is_success() {
154            return Err(RegistryError::Network(format!("Yank failed: {}", response.status())));
155        }
156
157        Ok(())
158    }
159}
160
161/// Publish request payload
162#[derive(Debug, Serialize, Deserialize)]
163pub struct PublishRequest {
164    pub name: String,
165    pub version: String,
166    pub description: String,
167    pub author: crate::AuthorInfo,
168    pub license: String,
169    pub repository: Option<String>,
170    pub homepage: Option<String>,
171    pub tags: Vec<String>,
172    pub category: crate::PluginCategory,
173    pub checksum: String,
174    pub size: u64,
175    pub min_mockforge_version: Option<String>,
176}
177
178/// Publish response
179#[derive(Debug, Serialize, Deserialize)]
180pub struct PublishResponse {
181    pub success: bool,
182    pub upload_url: String,
183    pub message: String,
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_client_creation() {
192        let config = RegistryConfig::default();
193        let client = RegistryClient::new(config);
194        assert!(client.is_ok());
195    }
196}