wasm_metadata/
dependencies.rs

1use std::fmt::{self, Display};
2use std::io::{read_to_string, Read};
3use std::str::FromStr;
4
5use anyhow::{ensure, Result};
6use auditable_serde::VersionInfo;
7use flate2::read::{ZlibDecoder, ZlibEncoder};
8use flate2::Compression;
9use serde::Serialize;
10use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
11use wasmparser::CustomSectionReader;
12
13/// Human-readable description of the binary
14#[derive(Debug, Clone, PartialEq)]
15pub struct Dependencies {
16    version_info: VersionInfo,
17    custom_section: CustomSection<'static>,
18}
19
20impl Dependencies {
21    /// Parse an `description` custom section from a wasm binary.
22    pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
23        ensure!(
24            reader.name() == ".dep-v0",
25            "The `dependencies` custom section should have a name of '.dep-v0'"
26        );
27        let decompressed_data = read_to_string(ZlibDecoder::new(reader.data()))?;
28        let dependency_tree = auditable_serde::VersionInfo::from_str(&decompressed_data)?;
29
30        Ok(Self {
31            version_info: dependency_tree,
32            custom_section: CustomSection {
33                name: ".dep-v0".into(),
34                data: reader.data().to_owned().into(),
35            },
36        })
37    }
38
39    /// Create a new instance of `Dependencies`.
40    pub fn new(dependency_tree: auditable_serde::VersionInfo) -> Self {
41        let data = serde_json::to_string(&dependency_tree).unwrap();
42
43        let mut ret_vec = Vec::new();
44        let mut encoder = ZlibEncoder::new(data.as_bytes(), Compression::fast());
45        encoder.read_to_end(&mut ret_vec).unwrap();
46
47        Self {
48            version_info: dependency_tree,
49            custom_section: CustomSection {
50                name: ".dep-v0".into(),
51                data: ret_vec.into(),
52            },
53        }
54    }
55
56    /// Provides access to the version information stored in the object
57    pub fn version_info(&self) -> &VersionInfo {
58        &self.version_info
59    }
60}
61
62impl Serialize for Dependencies {
63    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
64    where
65        S: serde::Serializer,
66    {
67        serializer.serialize_str(&self.to_string())
68    }
69}
70
71impl Display for Dependencies {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        // NOTE: this will never panic since we always guarantee the data is
74        // encoded as utf8, even if we internally store it as [u8].
75        // let data = String::from_utf8(self.0.data.to_vec()).unwrap();
76        let data = serde_json::to_string(&self.version_info).unwrap();
77        write!(f, "{}", data)
78    }
79}
80
81impl ComponentSection for Dependencies {
82    fn id(&self) -> u8 {
83        ComponentSection::id(&self.custom_section)
84    }
85}
86
87impl Section for Dependencies {
88    fn id(&self) -> u8 {
89        Section::id(&self.custom_section)
90    }
91}
92
93impl Encode for Dependencies {
94    fn encode(&self, sink: &mut Vec<u8>) {
95        self.custom_section.encode(sink);
96    }
97}
98
99#[cfg(test)]
100mod test {
101    use super::*;
102    use auditable_serde::{Source, VersionInfo};
103    use std::str::FromStr;
104    use wasm_encoder::Component;
105    use wasmparser::Payload;
106
107    #[test]
108    fn roundtrip() {
109        let json_str = r#"{"packages":[{"name":"adler","version":"0.2.3","source":"registry"}]}"#;
110        let info = VersionInfo::from_str(json_str).unwrap();
111        assert_eq!(&info.packages[0].name, "adler");
112        let mut component = Component::new();
113        component.section(&Dependencies::new(info));
114        let component = component.finish();
115
116        let mut parsed = false;
117        for section in wasmparser::Parser::new(0).parse_all(&component) {
118            if let Payload::CustomSection(reader) = section.unwrap() {
119                let dependencies = Dependencies::parse_custom_section(&reader).unwrap();
120                assert_eq!(dependencies.to_string(), json_str);
121                parsed = true;
122            }
123        }
124        assert!(parsed);
125    }
126
127    #[test]
128    fn serialize() {
129        let json_str = r#"{"packages":[{"name":"adler","version":"0.2.3","source":"registry"}]}"#;
130        let info = VersionInfo::from_str(json_str).unwrap();
131        let dependencies = Dependencies::new(info);
132        assert_eq!(dependencies.version_info().packages[0].name, "adler");
133        assert_eq!(
134            dependencies.version_info().packages[0].version.to_string(),
135            "0.2.3"
136        );
137        assert_eq!(
138            dependencies.version_info().packages[0].source,
139            Source::Registry,
140        );
141    }
142}