wasm_metadata/oci_annotations/
source.rs

1use std::borrow::Cow;
2use std::fmt::{self, Display};
3use std::str::FromStr;
4
5use anyhow::{ensure, Error, Result};
6use serde::Serialize;
7use url::Url;
8use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
9use wasmparser::CustomSectionReader;
10
11/// URL to get source code for building the image
12#[derive(Debug, Clone, PartialEq)]
13pub struct Source(CustomSection<'static>);
14
15impl Source {
16    /// Create a new instance of `Source`.
17    pub fn new(s: &str) -> Result<Self> {
18        Ok(Url::parse(s)?.into())
19    }
20
21    /// Parse a `source` custom section from a wasm binary.
22    pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
23        ensure!(
24            reader.name() == "source",
25            "The `source` custom section should have a name of 'source'"
26        );
27        let data = String::from_utf8(reader.data().to_owned())?;
28        Self::new(&data)
29    }
30}
31
32impl FromStr for Source {
33    type Err = Error;
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        Self::new(s)
37    }
38}
39
40impl From<Url> for Source {
41    fn from(expression: Url) -> Self {
42        Self(CustomSection {
43            name: "source".into(),
44            data: Cow::Owned(expression.to_string().into_bytes()),
45        })
46    }
47}
48
49impl Serialize for Source {
50    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
51    where
52        S: serde::Serializer,
53    {
54        serializer.serialize_str(&self.to_string())
55    }
56}
57
58impl Display for Source {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        // NOTE: this will never panic since we always guarantee the data is
61        // encoded as utf8, even if we internally store it as [u8].
62        let data = String::from_utf8(self.0.data.to_vec()).unwrap();
63        write!(f, "{data}")
64    }
65}
66
67impl ComponentSection for Source {
68    fn id(&self) -> u8 {
69        ComponentSection::id(&self.0)
70    }
71}
72
73impl Section for Source {
74    fn id(&self) -> u8 {
75        Section::id(&self.0)
76    }
77}
78
79impl Encode for Source {
80    fn encode(&self, sink: &mut Vec<u8>) {
81        self.0.encode(sink);
82    }
83}
84
85#[cfg(test)]
86mod test {
87    use super::*;
88    use wasm_encoder::Component;
89    use wasmparser::Payload;
90
91    #[test]
92    fn roundtrip() {
93        let mut component = Component::new();
94        component.section(&Source::new("https://github.com/bytecodealliance/wasm-tools").unwrap());
95        let component = component.finish();
96
97        let mut parsed = false;
98        for section in wasmparser::Parser::new(0).parse_all(&component) {
99            if let Payload::CustomSection(reader) = section.unwrap() {
100                let description = Source::parse_custom_section(&reader).unwrap();
101                assert_eq!(
102                    description.to_string(),
103                    "https://github.com/bytecodealliance/wasm-tools"
104                );
105                parsed = true;
106            }
107        }
108        assert!(parsed);
109    }
110
111    #[test]
112    fn serialize() {
113        let description = Source::new("https://github.com/bytecodealliance/wasm-tools").unwrap();
114        let json = serde_json::to_string(&description).unwrap();
115        assert_eq!(r#""https://github.com/bytecodealliance/wasm-tools""#, json);
116    }
117}