Wrap extension schema version in a newtype (#9872)

Marshall Bowers created

This PR wraps the extension schema version in a newtype, for some
additional type safety.

Release Notes:

- N/A

Change summary

crates/extension/src/extension_builder.rs    |  4 ++--
crates/extension/src/extension_manifest.rs   | 19 +++++++++++++++----
crates/extension/src/extension_store_test.rs |  7 ++++---
crates/extension_cli/src/main.rs             |  2 +-
4 files changed, 22 insertions(+), 10 deletions(-)

Detailed changes

crates/extension/src/extension_builder.rs 🔗

@@ -479,7 +479,7 @@ impl ExtensionBuilder {
 fn populate_defaults(manifest: &mut ExtensionManifest, extension_path: &Path) -> Result<()> {
     // For legacy extensions on the v0 schema (aka, using `extension.json`), clear out any existing
     // contents of the computed fields, since we don't care what the existing values are.
-    if manifest.schema_version == 0 {
+    if manifest.schema_version.is_v0() {
         manifest.languages.clear();
         manifest.grammars.clear();
         manifest.themes.clear();
@@ -522,7 +522,7 @@ fn populate_defaults(manifest: &mut ExtensionManifest, extension_path: &Path) ->
 
     // For legacy extensions on the v0 schema (aka, using `extension.json`), we want to populate the grammars in
     // the manifest using the contents of the `grammars` directory.
-    if manifest.schema_version == 0 {
+    if manifest.schema_version.is_v0() {
         let grammars_dir = extension_path.join("grammars");
         if grammars_dir.exists() {
             for entry in fs::read_dir(&grammars_dir).context("failed to list grammars dir")? {

crates/extension/src/extension_manifest.rs 🔗

@@ -11,7 +11,7 @@ use std::{
 use util::SemanticVersion;
 
 /// This is the old version of the extension manifest, from when it was `extension.json`.
-#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 pub struct OldExtensionManifest {
     pub name: String,
     pub version: Arc<str>,
@@ -31,12 +31,23 @@ pub struct OldExtensionManifest {
     pub grammars: BTreeMap<Arc<str>, PathBuf>,
 }
 
-#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
+pub struct SchemaVersion(pub i32);
+
+impl SchemaVersion {
+    pub const ZERO: Self = Self(0);
+
+    pub fn is_v0(&self) -> bool {
+        self == &Self::ZERO
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 pub struct ExtensionManifest {
     pub id: Arc<str>,
     pub name: String,
     pub version: Arc<str>,
-    pub schema_version: i32,
+    pub schema_version: SchemaVersion,
 
     #[serde(default)]
     pub description: Option<String>,
@@ -122,7 +133,7 @@ fn manifest_from_old_manifest(
         description: manifest_json.description,
         repository: manifest_json.repository,
         authors: manifest_json.authors,
-        schema_version: 0,
+        schema_version: SchemaVersion::ZERO,
         lib: Default::default(),
         themes: {
             let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();

crates/extension/src/extension_store_test.rs 🔗

@@ -1,3 +1,4 @@
+use crate::extension_manifest::SchemaVersion;
 use crate::{
     Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
     ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
@@ -146,7 +147,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
                         id: "zed-ruby".into(),
                         name: "Zed Ruby".into(),
                         version: "1.0.0".into(),
-                        schema_version: 0,
+                        schema_version: SchemaVersion::ZERO,
                         description: None,
                         authors: Vec::new(),
                         repository: None,
@@ -171,7 +172,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
                         id: "zed-monokai".into(),
                         name: "Zed Monokai".into(),
                         version: "2.0.0".into(),
-                        schema_version: 0,
+                        schema_version: SchemaVersion::ZERO,
                         description: None,
                         authors: vec![],
                         repository: None,
@@ -328,7 +329,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
                 id: "zed-gruvbox".into(),
                 name: "Zed Gruvbox".into(),
                 version: "1.0.0".into(),
-                schema_version: 0,
+                schema_version: SchemaVersion::ZERO,
                 description: None,
                 authors: vec![],
                 repository: None,

crates/extension_cli/src/main.rs 🔗

@@ -95,7 +95,7 @@ async fn main() -> Result<()> {
         version: manifest.version,
         description: manifest.description,
         authors: manifest.authors,
-        schema_version: Some(manifest.schema_version),
+        schema_version: Some(manifest.schema_version.0),
         repository: manifest
             .repository
             .ok_or_else(|| anyhow!("missing repository in extension manifest"))?,