1pub mod extension_builder;
2mod extension_manifest;
3
4use anyhow::{anyhow, bail, Context as _, Result};
5use semantic_version::SemanticVersion;
6
7pub use crate::extension_manifest::*;
8
9pub fn parse_wasm_extension_version(
10 extension_id: &str,
11 wasm_bytes: &[u8],
12) -> Result<SemanticVersion> {
13 let mut version = None;
14
15 for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
16 if let wasmparser::Payload::CustomSection(s) =
17 part.context("error parsing wasm extension")?
18 {
19 if s.name() == "zed:api-version" {
20 version = parse_wasm_extension_version_custom_section(s.data());
21 if version.is_none() {
22 bail!(
23 "extension {} has invalid zed:api-version section: {:?}",
24 extension_id,
25 s.data()
26 );
27 }
28 }
29 }
30 }
31
32 // The reason we wait until we're done parsing all of the Wasm bytes to return the version
33 // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
34 //
35 // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
36 // earlier as an `Err` rather than as a panic.
37 version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
38}
39
40fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
41 if data.len() == 6 {
42 Some(SemanticVersion::new(
43 u16::from_be_bytes([data[0], data[1]]) as _,
44 u16::from_be_bytes([data[2], data[3]]) as _,
45 u16::from_be_bytes([data[4], data[5]]) as _,
46 ))
47 } else {
48 None
49 }
50}