Add a schema to extensions, to prevent installing extensions on too old of a Zed version (#9599)
Release Notes: - N/A --------- Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
b1feeb9f29
commit
585e8671e3
22 changed files with 165 additions and 44 deletions
|
@ -373,6 +373,7 @@ CREATE TABLE extension_versions (
|
|||
authors TEXT NOT NULL,
|
||||
repository TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
schema_version INTEGER NOT NULL DEFAULT 0,
|
||||
download_count INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (extension_id, version)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- Add migration script here
|
||||
ALTER TABLE extension_versions ADD COLUMN schema_version INTEGER NOT NULL DEFAULT 0;
|
|
@ -12,6 +12,7 @@ use axum::{
|
|||
Extension, Json, Router,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use rpc::ExtensionApiManifest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use time::PrimitiveDateTime;
|
||||
|
@ -33,6 +34,8 @@ pub fn router() -> Router {
|
|||
#[derive(Debug, Deserialize)]
|
||||
struct GetExtensionsParams {
|
||||
filter: Option<String>,
|
||||
#[serde(default)]
|
||||
max_schema_version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -51,20 +54,14 @@ struct GetExtensionsResponse {
|
|||
pub data: Vec<ExtensionMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtensionManifest {
|
||||
name: String,
|
||||
version: String,
|
||||
description: Option<String>,
|
||||
authors: Vec<String>,
|
||||
repository: String,
|
||||
}
|
||||
|
||||
async fn get_extensions(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Query(params): Query<GetExtensionsParams>,
|
||||
) -> Result<Json<GetExtensionsResponse>> {
|
||||
let extensions = app.db.get_extensions(params.filter.as_deref(), 500).await?;
|
||||
let extensions = app
|
||||
.db
|
||||
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
|
||||
.await?;
|
||||
Ok(Json(GetExtensionsResponse { data: extensions }))
|
||||
}
|
||||
|
||||
|
@ -267,7 +264,7 @@ async fn fetch_extension_manifest(
|
|||
})?
|
||||
.to_vec();
|
||||
let manifest =
|
||||
serde_json::from_slice::<ExtensionManifest>(&manifest_bytes).with_context(|| {
|
||||
serde_json::from_slice::<ExtensionApiManifest>(&manifest_bytes).with_context(|| {
|
||||
format!(
|
||||
"invalid manifest for extension {extension_id} version {version}: {}",
|
||||
String::from_utf8_lossy(&manifest_bytes)
|
||||
|
@ -287,6 +284,7 @@ async fn fetch_extension_manifest(
|
|||
description: manifest.description.unwrap_or_default(),
|
||||
authors: manifest.authors,
|
||||
repository: manifest.repository,
|
||||
schema_version: manifest.schema_version.unwrap_or(0),
|
||||
published_at,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -731,6 +731,7 @@ pub struct NewExtensionVersion {
|
|||
pub description: String,
|
||||
pub authors: Vec<String>,
|
||||
pub repository: String,
|
||||
pub schema_version: i32,
|
||||
pub published_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
|
|
|
@ -4,27 +4,28 @@ impl Database {
|
|||
pub async fn get_extensions(
|
||||
&self,
|
||||
filter: Option<&str>,
|
||||
max_schema_version: i32,
|
||||
limit: usize,
|
||||
) -> Result<Vec<ExtensionMetadata>> {
|
||||
self.transaction(|tx| async move {
|
||||
let mut condition = Condition::all();
|
||||
let mut condition = Condition::all().add(
|
||||
extension::Column::LatestVersion
|
||||
.into_expr()
|
||||
.eq(extension_version::Column::Version.into_expr()),
|
||||
);
|
||||
if let Some(filter) = filter {
|
||||
let fuzzy_name_filter = Self::fuzzy_like_string(filter);
|
||||
condition = condition.add(Expr::cust_with_expr("name ILIKE $1", fuzzy_name_filter));
|
||||
}
|
||||
|
||||
let extensions = extension::Entity::find()
|
||||
.inner_join(extension_version::Entity)
|
||||
.select_also(extension_version::Entity)
|
||||
.filter(condition)
|
||||
.filter(extension_version::Column::SchemaVersion.lte(max_schema_version))
|
||||
.order_by_desc(extension::Column::TotalDownloadCount)
|
||||
.order_by_asc(extension::Column::Name)
|
||||
.limit(Some(limit as u64))
|
||||
.filter(
|
||||
extension::Column::LatestVersion
|
||||
.into_expr()
|
||||
.eq(extension_version::Column::Version.into_expr()),
|
||||
)
|
||||
.inner_join(extension_version::Entity)
|
||||
.select_also(extension_version::Entity)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
|
@ -170,6 +171,7 @@ impl Database {
|
|||
authors: ActiveValue::Set(version.authors.join(", ")),
|
||||
repository: ActiveValue::Set(version.repository.clone()),
|
||||
description: ActiveValue::Set(version.description.clone()),
|
||||
schema_version: ActiveValue::Set(version.schema_version),
|
||||
download_count: ActiveValue::NotSet,
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -13,6 +13,7 @@ pub struct Model {
|
|||
pub authors: String,
|
||||
pub repository: String,
|
||||
pub description: String,
|
||||
pub schema_version: i32,
|
||||
pub download_count: i64,
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
let versions = db.get_known_extension_versions().await.unwrap();
|
||||
assert!(versions.is_empty());
|
||||
|
||||
let extensions = db.get_extensions(None, 5).await.unwrap();
|
||||
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
|
||||
assert!(extensions.is_empty());
|
||||
|
||||
let t0 = OffsetDateTime::from_unix_timestamp_nanos(0).unwrap();
|
||||
|
@ -33,6 +33,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
description: "an extension".into(),
|
||||
authors: vec!["max".into()],
|
||||
repository: "ext1/repo".into(),
|
||||
schema_version: 1,
|
||||
published_at: t0,
|
||||
},
|
||||
NewExtensionVersion {
|
||||
|
@ -41,6 +42,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
description: "a good extension".into(),
|
||||
authors: vec!["max".into(), "marshall".into()],
|
||||
repository: "ext1/repo".into(),
|
||||
schema_version: 1,
|
||||
published_at: t0,
|
||||
},
|
||||
],
|
||||
|
@ -53,6 +55,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
description: "a great extension".into(),
|
||||
authors: vec!["marshall".into()],
|
||||
repository: "ext2/repo".into(),
|
||||
schema_version: 0,
|
||||
published_at: t0,
|
||||
}],
|
||||
),
|
||||
|
@ -75,7 +78,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
);
|
||||
|
||||
// The latest version of each extension is returned.
|
||||
let extensions = db.get_extensions(None, 5).await.unwrap();
|
||||
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
|
||||
assert_eq!(
|
||||
extensions,
|
||||
&[
|
||||
|
@ -102,6 +105,22 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
]
|
||||
);
|
||||
|
||||
// Extensions with too new of a schema version are excluded.
|
||||
let extensions = db.get_extensions(None, 0, 5).await.unwrap();
|
||||
assert_eq!(
|
||||
extensions,
|
||||
&[ExtensionMetadata {
|
||||
id: "ext2".into(),
|
||||
name: "Extension Two".into(),
|
||||
version: "0.2.0".into(),
|
||||
authors: vec!["marshall".into()],
|
||||
description: "a great extension".into(),
|
||||
repository: "ext2/repo".into(),
|
||||
published_at: t0,
|
||||
download_count: 0
|
||||
},]
|
||||
);
|
||||
|
||||
// Record extensions being downloaded.
|
||||
for _ in 0..7 {
|
||||
assert!(db.record_extension_download("ext2", "0.0.2").await.unwrap());
|
||||
|
@ -122,7 +141,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
.unwrap());
|
||||
|
||||
// Extensions are returned in descending order of total downloads.
|
||||
let extensions = db.get_extensions(None, 5).await.unwrap();
|
||||
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
|
||||
assert_eq!(
|
||||
extensions,
|
||||
&[
|
||||
|
@ -161,6 +180,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
description: "a real good extension".into(),
|
||||
authors: vec!["max".into(), "marshall".into()],
|
||||
repository: "ext1/repo".into(),
|
||||
schema_version: 1,
|
||||
published_at: t0,
|
||||
}],
|
||||
),
|
||||
|
@ -172,6 +192,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
description: "an old extension".into(),
|
||||
authors: vec!["marshall".into()],
|
||||
repository: "ext2/repo".into(),
|
||||
schema_version: 0,
|
||||
published_at: t0,
|
||||
}],
|
||||
),
|
||||
|
@ -196,7 +217,7 @@ async fn test_extensions(db: &Arc<Database>) {
|
|||
.collect()
|
||||
);
|
||||
|
||||
let extensions = db.get_extensions(None, 5).await.unwrap();
|
||||
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
|
||||
assert_eq!(
|
||||
extensions,
|
||||
&[
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue