wasm_metadata/oci_annotations/
licenses.rs

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