use serde::Deserialize;
use super::SourceAdapter;
use crate::model::{Match, Query, Source};
use crate::Result;
const DEFAULT_BASE_URL: &str = "https://marketplace.visualstudio.com";
#[derive(Debug, Clone)]
pub struct VsCodeMarketplace {
client: reqwest::Client,
base_url: String,
}
impl VsCodeMarketplace {
pub fn new(client: reqwest::Client) -> Self {
Self::with_base_url(client, DEFAULT_BASE_URL.to_string())
}
pub fn with_base_url(client: reqwest::Client, base_url: String) -> Self {
Self { client, base_url }
}
}
#[derive(Debug, Deserialize)]
struct GalleryResponse {
results: Vec<GalleryResult>,
}
#[derive(Debug, Deserialize)]
struct GalleryResult {
extensions: Vec<Extension>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Extension {
publisher: Publisher,
extension_name: String,
#[serde(default)]
display_name: Option<String>,
#[serde(default)]
short_description: Option<String>,
#[serde(default)]
statistics: Vec<Statistic>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Publisher {
publisher_name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Statistic {
statistic_name: String,
value: f64,
}
#[async_trait::async_trait]
impl SourceAdapter for VsCodeMarketplace {
fn id(&self) -> Source {
Source::VsCodeMarketplace
}
async fn search(&self, query: &Query) -> Result<Vec<Match>> {
let url = format!("{}/_apis/public/gallery/extensionquery", self.base_url);
let q = query.keywords.join(" ");
let body = serde_json::json!({
"filters": [{
"criteria": [
{ "filterType": 8, "value": "Microsoft.VisualStudio.Code" },
{ "filterType": 10, "value": q },
],
"pageSize": 20,
"pageNumber": 1,
}],
"flags": 914
});
let resp: GalleryResponse = self
.client
.post(&url)
.header("Accept", "application/json;api-version=7.1-preview.1")
.json(&body)
.send()
.await?
.error_for_status()?
.json()
.await?;
let extensions = match resp.results.into_iter().next() {
Some(r) => r.extensions,
None => return Ok(vec![]),
};
Ok(extensions
.into_iter()
.filter(|e| e.short_description.as_ref().is_some_and(|d| !d.is_empty()))
.map(|e| {
let installs = e
.statistics
.iter()
.find(|s| s.statistic_name == "install")
.map(|s| s.value as u64);
let display = e.display_name.unwrap_or_else(|| e.extension_name.clone());
let url = format!(
"https://marketplace.visualstudio.com/items?itemName={}.{}",
e.publisher.publisher_name, e.extension_name,
);
Match {
name: display,
source: Source::VsCodeMarketplace,
url,
description: e.short_description.unwrap_or_default(),
popularity: installs,
similarity: 0.0,
}
})
.collect())
}
}